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 | TpmHash::Sha3_512 => MessageDigest::sm3(),
78            TpmHash::Sha3_256 => MessageDigest::sha3_256(),
79            TpmHash::Sha3_384 => MessageDigest::sha3_384(),
80            TpmHash::Shake128 => MessageDigest::shake_128(),
81            TpmHash::Shake256 => MessageDigest::shake_256(),
82            TpmHash::Null => MessageDigest::null(),
83        }
84    }
85}
86
87impl TpmHash {
88    /// Returns the size of the digest size.
89    #[must_use]
90    pub fn size(&self) -> usize {
91        Into::<MessageDigest>::into(*self).size()
92    }
93
94    /// Computes a cryptographic digest over a series of data chunks.
95    ///
96    /// # Errors
97    ///
98    /// Returns [`InvalidHash`](crate::Error::InvalidHash)
99    /// when the hash algorithm is not recognized.
100    /// Returns [`OperationFailed`](crate::Error::OperationFailed)
101    /// when the digest computation fails.
102    /// Returns [`OutOfMemory`](crate::Error::OutOfMemory) when an allocation fails.
103    pub fn digest(&self, data_chunks: &[&[u8]]) -> Result<Vec<u8>, TpmCryptoError> {
104        let md = (*self).into();
105        let mut hasher = Hasher::new(md).map_err(|_| TpmCryptoError::OutOfMemory)?;
106        for chunk in data_chunks {
107            hasher
108                .update(chunk)
109                .map_err(|_| TpmCryptoError::OperationFailed)?;
110        }
111        Ok(hasher
112            .finish()
113            .map_err(|_| TpmCryptoError::OperationFailed)?
114            .to_vec())
115    }
116
117    /// Computes an HMAC digest over a series of data chunks.
118    ///
119    /// # Errors
120    ///
121    /// Returns [`InvalidHash`](crate::Error::InvalidHash)
122    /// when the hash algorithm is not recognized.
123    /// Returns [`KeyIsEmpty`](crate::Error::KeyIsEmpty) when the provided key is empty.
124    /// Returns [`OperationFailed`](crate::Error::OperationFailed)
125    /// when the HMAC computation fails.
126    /// Returns [`OutOfMemory`](crate::Error::OutOfMemory) when an allocation fails.
127    pub fn hmac(&self, key: &[u8], data_chunks: &[&[u8]]) -> Result<Vec<u8>, TpmCryptoError> {
128        if key.is_empty() {
129            return Err(TpmCryptoError::KeyIsEmpty);
130        }
131        let md = (*self).into();
132        let public_key = PKey::hmac(key).map_err(|_| TpmCryptoError::OutOfMemory)?;
133        let mut signer = Signer::new(md, &public_key).map_err(|_| TpmCryptoError::OutOfMemory)?;
134        for chunk in data_chunks {
135            signer
136                .update(chunk)
137                .map_err(|_| TpmCryptoError::OperationFailed)?;
138        }
139        signer
140            .sign_to_vec()
141            .map_err(|_| TpmCryptoError::OperationFailed)
142    }
143
144    /// Verifies an HMAC signature over a series of data chunks.
145    ///
146    /// # Errors
147    ///
148    /// Returns [`PermissionDenied`](crate::Error::PermissionDenied)
149    /// when the HMAC does not match the expected value.
150    /// Returns [`InvalidHash`](crate::Error::InvalidHash)
151    /// when the hash algorithm is not recognized.
152    /// Returns [`OperationFailed`](crate::Error::OperationFailed)
153    /// when the HMAC computation fails.
154    /// Returns [`OutOfMemory`](crate::Error::OutOfMemory) when an allocation fails.
155    pub fn hmac_verify(
156        &self,
157        key: &[u8],
158        data_chunks: &[&[u8]],
159        signature: &[u8],
160    ) -> Result<(), TpmCryptoError> {
161        let expected = self.hmac(key, data_chunks)?;
162        if memcmp::eq(&expected, signature) {
163            Ok(())
164        } else {
165            Err(TpmCryptoError::PermissionDenied)
166        }
167    }
168
169    /// Implements the `KDFa` key derivation function from the TPM specification.
170    ///
171    /// # Errors
172    ///
173    /// Returns [`InvalidHash`](crate::Error::InvalidHash)
174    /// when the hash algorithm is not recognized.
175    /// Returns [`KeyIsEmpty`](crate::Error::KeyIsEmpty) when the provided key is empty.
176    /// Returns [`OperationFailed`](crate::Error::OperationFailed)
177    /// when the HMAC computation fails.
178    /// Returns [`OutOfMemory`](crate::Error::OutOfMemory) when an allocation fails.
179    pub fn kdfa(
180        &self,
181        hmac_key: &[u8],
182        label: &str,
183        context_a: &[u8],
184        context_b: &[u8],
185        key_bits: u16,
186    ) -> Result<Vec<u8>, TpmCryptoError> {
187        if hmac_key.is_empty() {
188            return Err(TpmCryptoError::KeyIsEmpty);
189        }
190
191        let key_bytes = (key_bits as usize).div_ceil(8);
192        let mut key_stream = Vec::with_capacity(key_bytes);
193
194        let mut counter: u32 = 1;
195        let key_bits_bytes = u32::from(key_bits).to_be_bytes();
196
197        let md = (*self).into();
198        let pkey = PKey::hmac(hmac_key).map_err(|_| TpmCryptoError::OutOfMemory)?;
199
200        while key_stream.len() < key_bytes {
201            let counter_bytes = counter.to_be_bytes();
202            let hmac_payload = [
203                counter_bytes.as_slice(),
204                label.as_bytes(),
205                &[0u8],
206                context_a,
207                context_b,
208                key_bits_bytes.as_slice(),
209            ];
210
211            let mut signer = Signer::new(md, &pkey).map_err(|_| TpmCryptoError::OutOfMemory)?;
212            for chunk in &hmac_payload {
213                signer
214                    .update(chunk)
215                    .map_err(|_| TpmCryptoError::OperationFailed)?;
216            }
217            let result = signer
218                .sign_to_vec()
219                .map_err(|_| TpmCryptoError::OperationFailed)?;
220
221            let remaining = key_bytes - key_stream.len();
222            let to_take = remaining.min(result.len());
223            key_stream.extend_from_slice(&result[..to_take]);
224
225            counter += 1;
226        }
227
228        Ok(key_stream)
229    }
230
231    /// Implements the `KDFe` key derivation function from SP 800-56A for ECDH.
232    ///
233    /// # Errors
234    ///
235    /// Returns [`InvalidHash`](crate::Error::InvalidHash)
236    /// when the hash algorithm is not recognized.
237    /// Returns [`OperationFailed`](crate::Error::OperationFailed)
238    /// when the digest computation fails.
239    /// Returns [`OutOfMemory`](crate::Error::OutOfMemory) when an allocation fails.
240    pub fn kdfe(
241        &self,
242        z: &[u8],
243        label: &str,
244        context_u: &[u8],
245        context_v: &[u8],
246        key_bits: u16,
247    ) -> Result<Vec<u8>, TpmCryptoError> {
248        let key_bytes = (key_bits as usize).div_ceil(8);
249        let mut key_stream = Vec::with_capacity(key_bytes);
250
251        let (label_data, terminator) = if label.as_bytes().last() == Some(&0) {
252            (label.as_bytes(), &[][..])
253        } else {
254            (label.as_bytes(), &[0u8][..])
255        };
256
257        let mut counter: u32 = 1;
258        let md = (*self).into();
259
260        while key_stream.len() < key_bytes {
261            let counter_bytes = counter.to_be_bytes();
262            let digest_payload = [
263                &counter_bytes,
264                z,
265                label_data,
266                terminator,
267                context_u,
268                context_v,
269            ];
270
271            let mut hasher = Hasher::new(md).map_err(|_| TpmCryptoError::OutOfMemory)?;
272            for chunk in &digest_payload {
273                hasher
274                    .update(chunk)
275                    .map_err(|_| TpmCryptoError::OperationFailed)?;
276            }
277            let result = hasher
278                .finish()
279                .map_err(|_| TpmCryptoError::OperationFailed)?;
280
281            let remaining = key_bytes - key_stream.len();
282            let to_take = remaining.min(result.len());
283            key_stream.extend_from_slice(&result[..to_take]);
284
285            counter += 1;
286        }
287
288        Ok(key_stream)
289    }
290}