tpm2_crypto/
hash.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2025 Opinsys Oy
3// Copyright (c) 2024-2025 Jarkko Sakkinen
4
5//! TPM 2.0 hash algorithms and cryptographic operations.
6
7use crate::TpmCryptoError;
8use openssl::{
9    hash::{Hasher, MessageDigest},
10    memcmp,
11    pkey::PKey,
12    sign::Signer,
13};
14use strum::{Display, EnumString};
15use tpm2_protocol::data::TpmAlgId;
16
17/// TPM 2.0 hash algorithms.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)]
19#[strum(serialize_all = "kebab-case")]
20pub enum TpmHash {
21    Sha1,
22    Sha256,
23    Sha384,
24    Sha512,
25    Sm3_256,
26    Sha3_256,
27    Sha3_384,
28    Sha3_512,
29    Shake128,
30    Shake256,
31    Null,
32}
33
34impl From<TpmAlgId> for TpmHash {
35    fn from(alg: TpmAlgId) -> Self {
36        match alg {
37            TpmAlgId::Sha1 => Self::Sha1,
38            TpmAlgId::Sha256 => Self::Sha256,
39            TpmAlgId::Sha384 => Self::Sha384,
40            TpmAlgId::Sha512 => Self::Sha512,
41            TpmAlgId::Sm3_256 => Self::Sm3_256,
42            TpmAlgId::Sha3_256 => Self::Sha3_256,
43            TpmAlgId::Sha3_384 => Self::Sha3_384,
44            TpmAlgId::Sha3_512 => Self::Sha3_512,
45            TpmAlgId::Shake128 => Self::Shake128,
46            TpmAlgId::Shake256 => Self::Shake256,
47            _ => Self::Null,
48        }
49    }
50}
51
52impl From<TpmHash> for TpmAlgId {
53    fn from(alg: TpmHash) -> Self {
54        match alg {
55            TpmHash::Sha1 => Self::Sha1,
56            TpmHash::Sha256 => Self::Sha256,
57            TpmHash::Sha384 => Self::Sha384,
58            TpmHash::Sha512 => Self::Sha512,
59            TpmHash::Sm3_256 => Self::Sm3_256,
60            TpmHash::Sha3_256 => Self::Sha3_256,
61            TpmHash::Sha3_384 => Self::Sha3_384,
62            TpmHash::Sha3_512 => Self::Sha3_512,
63            TpmHash::Shake128 => Self::Shake128,
64            TpmHash::Shake256 => Self::Shake256,
65            TpmHash::Null => Self::Null,
66        }
67    }
68}
69
70impl From<TpmHash> for MessageDigest {
71    fn from(alg: TpmHash) -> Self {
72        match alg {
73            TpmHash::Sha1 => MessageDigest::sha1(),
74            TpmHash::Sha256 => MessageDigest::sha256(),
75            TpmHash::Sha384 => MessageDigest::sha384(),
76            TpmHash::Sha512 => MessageDigest::sha512(),
77            TpmHash::Sm3_256 => MessageDigest::sm3(),
78            TpmHash::Sha3_256 => MessageDigest::sha3_256(),
79            TpmHash::Sha3_384 => MessageDigest::sha3_384(),
80            TpmHash::Sha3_512 => MessageDigest::sha3_512(),
81            TpmHash::Shake128 => MessageDigest::shake_128(),
82            TpmHash::Shake256 => MessageDigest::shake_256(),
83            TpmHash::Null => MessageDigest::null(),
84        }
85    }
86}
87
88impl TpmHash {
89    /// Returns the size of the digest size.
90    #[must_use]
91    pub fn size(&self) -> usize {
92        Into::<MessageDigest>::into(*self).size()
93    }
94
95    /// Computes a cryptographic digest over a series of data chunks.
96    ///
97    /// # Errors
98    ///
99    /// Returns [`InvalidHash`](crate::TpmCryptoError::InvalidHash) when the
100    /// hash algorithm is not recognized.
101    /// Returns [`OperationFailed`](crate::TpmCryptoError::OperationFailed)
102    /// when the digest computation fails.
103    /// Returns [`OutOfMemory`](crate::TpmCryptoError::OutOfMemory) when an
104    /// allocation fails.
105    pub fn digest(&self, data_chunks: &[&[u8]]) -> Result<Vec<u8>, TpmCryptoError> {
106        if *self == TpmHash::Null {
107            return Err(TpmCryptoError::InvalidHash);
108        }
109
110        let md = (*self).into();
111        let mut hasher = Hasher::new(md).map_err(|_| TpmCryptoError::OutOfMemory)?;
112        for chunk in data_chunks {
113            hasher
114                .update(chunk)
115                .map_err(|_| TpmCryptoError::OperationFailed)?;
116        }
117        Ok(hasher
118            .finish()
119            .map_err(|_| TpmCryptoError::OperationFailed)?
120            .to_vec())
121    }
122
123    /// Computes an HMAC digest over a series of data chunks.
124    ///
125    /// # Errors
126    ///
127    /// Returns [`InvalidHash`](crate::TpmCryptoError::InvalidHash) when the
128    /// hash algorithm is not recognized.
129    /// Returns [`KeyIsEmpty`](crate::TpmCryptoError::KeyIsEmpty) when the
130    /// provided key is empty.
131    /// Returns [`OperationFailed`](crate::TpmCryptoError::OperationFailed)
132    /// when the HMAC computation fails.
133    /// Returns [`OutOfMemory`](crate::TpmCryptoError::OutOfMemory) when an
134    /// allocation fails.
135    pub fn hmac(&self, key: &[u8], data_chunks: &[&[u8]]) -> Result<Vec<u8>, TpmCryptoError> {
136        if *self == TpmHash::Null {
137            return Err(TpmCryptoError::InvalidHash);
138        }
139        if key.is_empty() {
140            return Err(TpmCryptoError::KeyIsEmpty);
141        }
142        let md = (*self).into();
143        let public_key = PKey::hmac(key).map_err(|_| TpmCryptoError::OutOfMemory)?;
144        let mut signer = Signer::new(md, &public_key).map_err(|_| TpmCryptoError::OutOfMemory)?;
145        for chunk in data_chunks {
146            signer
147                .update(chunk)
148                .map_err(|_| TpmCryptoError::OperationFailed)?;
149        }
150        signer
151            .sign_to_vec()
152            .map_err(|_| TpmCryptoError::OperationFailed)
153    }
154
155    /// Verifies an HMAC signature over a series of data chunks.
156    ///
157    /// # Errors
158    ///
159    /// Returns [`PermissionDenied`](crate::TpmCryptoError::PermissionDenied)
160    /// when the HMAC does not match the expected value.
161    /// Returns [`InvalidHash`](crate::TpmCryptoError::InvalidHash) when the
162    /// hash algorithm is not recognized.
163    /// Returns [`OperationFailed`](crate::TpmCryptoError::OperationFailed)
164    /// when the HMAC computation fails.
165    /// Returns [`OutOfMemory`](crate::TpmCryptoError::OutOfMemory) when an
166    /// allocation fails.
167    pub fn hmac_verify(
168        &self,
169        key: &[u8],
170        data_chunks: &[&[u8]],
171        signature: &[u8],
172    ) -> Result<(), TpmCryptoError> {
173        let expected = self.hmac(key, data_chunks)?;
174        if memcmp::eq(&expected, signature) {
175            Ok(())
176        } else {
177            Err(TpmCryptoError::PermissionDenied)
178        }
179    }
180
181    /// Implements the `KDFa` key derivation function from the TPM
182    /// specification.
183    ///
184    /// # Errors
185    ///
186    /// Returns [`InvalidHash`](crate::TpmCryptoError::InvalidHash) when the
187    /// hash algorithm is not recognized.
188    /// Returns [`KeyIsEmpty`](crate::TpmCryptoError::KeyIsEmpty) when the
189    /// provided key is empty.
190    /// Returns [`OperationFailed`](crate::TpmCryptoError::OperationFailed)
191    /// when the HMAC computation fails.
192    /// Returns [`OutOfMemory`](crate::TpmCryptoError::OutOfMemory) when an
193    /// allocation fails.
194    pub fn kdfa(
195        &self,
196        hmac_key: &[u8],
197        label: &str,
198        context_a: &[u8],
199        context_b: &[u8],
200        key_bits: u16,
201    ) -> Result<Vec<u8>, TpmCryptoError> {
202        if *self == TpmHash::Null {
203            return Err(TpmCryptoError::InvalidHash);
204        }
205        if hmac_key.is_empty() {
206            return Err(TpmCryptoError::KeyIsEmpty);
207        }
208
209        let key_bytes = (key_bits as usize).div_ceil(8);
210        let mut key_stream = Vec::with_capacity(key_bytes);
211
212        let mut counter: u32 = 1;
213        let key_bits_bytes = u32::from(key_bits).to_be_bytes();
214
215        let md = (*self).into();
216        let pkey = PKey::hmac(hmac_key).map_err(|_| TpmCryptoError::OutOfMemory)?;
217
218        while key_stream.len() < key_bytes {
219            let counter_bytes = counter.to_be_bytes();
220            let hmac_payload = [
221                counter_bytes.as_slice(),
222                label.as_bytes(),
223                &[0u8],
224                context_a,
225                context_b,
226                key_bits_bytes.as_slice(),
227            ];
228
229            let mut signer = Signer::new(md, &pkey).map_err(|_| TpmCryptoError::OutOfMemory)?;
230            for chunk in &hmac_payload {
231                signer
232                    .update(chunk)
233                    .map_err(|_| TpmCryptoError::OperationFailed)?;
234            }
235            let result = signer
236                .sign_to_vec()
237                .map_err(|_| TpmCryptoError::OperationFailed)?;
238
239            let remaining = key_bytes - key_stream.len();
240            let to_take = remaining.min(result.len());
241            key_stream.extend_from_slice(&result[..to_take]);
242
243            counter += 1;
244        }
245
246        Ok(key_stream)
247    }
248
249    /// Implements the `KDFe` key derivation function from SP 800-56A for ECDH.
250    ///
251    /// # Errors
252    ///
253    /// Returns [`InvalidHash`](crate::TpmCryptoError::InvalidHash) when the
254    /// hash algorithm is not recognized.
255    /// Returns [`OperationFailed`](crate::TpmCryptoError::OperationFailed)
256    /// when the digest computation fails.
257    /// Returns [`OutOfMemory`](crate::TpmCryptoError::OutOfMemory) when an
258    /// allocation fails.
259    pub fn kdfe(
260        &self,
261        z: &[u8],
262        label: &str,
263        context_u: &[u8],
264        context_v: &[u8],
265        key_bits: u16,
266    ) -> Result<Vec<u8>, TpmCryptoError> {
267        if *self == TpmHash::Null {
268            return Err(TpmCryptoError::InvalidHash);
269        }
270
271        let key_bytes = (key_bits as usize).div_ceil(8);
272        let mut key_stream = Vec::with_capacity(key_bytes);
273
274        let (label_data, terminator) = if label.as_bytes().last() == Some(&0) {
275            (label.as_bytes(), &[][..])
276        } else {
277            (label.as_bytes(), &[0u8][..])
278        };
279
280        let mut counter: u32 = 1;
281        let md = (*self).into();
282
283        while key_stream.len() < key_bytes {
284            let counter_bytes = counter.to_be_bytes();
285            let digest_payload = [
286                &counter_bytes,
287                z,
288                label_data,
289                terminator,
290                context_u,
291                context_v,
292            ];
293
294            let mut hasher = Hasher::new(md).map_err(|_| TpmCryptoError::OutOfMemory)?;
295            for chunk in &digest_payload {
296                hasher
297                    .update(chunk)
298                    .map_err(|_| TpmCryptoError::OperationFailed)?;
299            }
300            let result = hasher
301                .finish()
302                .map_err(|_| TpmCryptoError::OperationFailed)?;
303
304            let remaining = key_bytes - key_stream.len();
305            let to_take = remaining.min(result.len());
306            key_stream.extend_from_slice(&result[..to_take]);
307
308            counter += 1;
309        }
310
311        Ok(key_stream)
312    }
313}