1use crate::convert::from_tpm_object_to_vec;
8use hmac::{Hmac, Mac};
9use num_traits::FromPrimitive;
10use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
11use rand::{CryptoRng, RngCore};
12use rsa::Oaep;
13use sha1::Sha1;
14use sha2::{Digest, Sha256, Sha384, Sha512};
15use thiserror::Error;
16use tpm2_protocol::{
17 data::{
18 Tpm2bEccParameter, Tpm2bEncryptedSecret, Tpm2bName, TpmAlgId, TpmEccCurve, TpmsEccPoint,
19 TpmtPublic, TpmuPublicId, TpmuPublicParms,
20 },
21 tpm_hash_size, TpmErrorKind,
22};
23
24pub const UNCOMPRESSED_POINT_TAG: u8 = 0x04;
25
26pub const KDF_LABEL_DUPLICATE: &str = "DUPLICATE";
27pub const KDF_LABEL_INTEGRITY: &str = "INTEGRITY";
28pub const KDF_LABEL_STORAGE: &str = "STORAGE";
29
30#[derive(Debug, Error)]
31pub enum CryptoError {
32 #[error("unsupported or invalid hash algorithm")]
33 InvalidHashAlgorithm,
34 #[error("invalid cryptographic key")]
35 InvalidKey,
36 #[error("unsupported or invalid cryptographic scheme")]
37 InvalidScheme,
38 #[error("MAC verification failed")]
39 MacVerificationFailed,
40 #[error("RSA operation failed: {0}")]
41 RsaOperationFailed(String),
42 #[error("DER encoding failed: {0}")]
43 DerEncodingFailed(String),
44 #[error("unsupported elliptic curve")]
45 UnsupportedCurve,
46 #[error("invalid elliptic curve point")]
47 InvalidEccPoint,
48 #[error("value conversion failed: {0}")]
49 ValueConversionFailed(String),
50 #[error("TPM: {0}")]
51 Tpm(TpmErrorKind),
52}
53
54impl From<TpmErrorKind> for CryptoError {
55 fn from(err: TpmErrorKind) -> Self {
56 Self::Tpm(err)
57 }
58}
59
60pub fn crypto_digest(alg: TpmAlgId, data_chunks: &[&[u8]]) -> Result<Vec<u8>, CryptoError> {
66 macro_rules! digest {
67 ($hasher:ty) => {{
68 let mut hasher = <$hasher>::new();
69 for chunk in data_chunks {
70 hasher.update(chunk);
71 }
72 Ok(hasher.finalize().to_vec())
73 }};
74 }
75
76 match alg {
77 TpmAlgId::Sha1 => digest!(Sha1),
78 TpmAlgId::Sha256 => digest!(Sha256),
79 TpmAlgId::Sha384 => digest!(Sha384),
80 TpmAlgId::Sha512 => digest!(Sha512),
81 _ => Err(CryptoError::InvalidHashAlgorithm),
82 }
83}
84
85pub fn crypto_hmac(
91 alg: TpmAlgId,
92 key: &[u8],
93 data_chunks: &[&[u8]],
94) -> Result<Vec<u8>, CryptoError> {
95 macro_rules! hmac {
96 ($digest:ty) => {{
97 let mut mac =
98 <Hmac<$digest> as Mac>::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
99 for chunk in data_chunks {
100 mac.update(chunk);
101 }
102 Ok(mac.finalize().into_bytes().to_vec())
103 }};
104 }
105
106 match alg {
107 TpmAlgId::Sha256 => hmac!(Sha256),
108 TpmAlgId::Sha384 => hmac!(Sha384),
109 TpmAlgId::Sha512 => hmac!(Sha512),
110 _ => Err(CryptoError::InvalidHashAlgorithm),
111 }
112}
113
114pub fn crypto_hmac_verify(
121 alg: TpmAlgId,
122 key: &[u8],
123 data_chunks: &[&[u8]],
124 signature: &[u8],
125) -> Result<(), CryptoError> {
126 macro_rules! verify_hmac {
127 ($digest:ty) => {{
128 let mut mac =
129 <Hmac<$digest> as Mac>::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
130 for chunk in data_chunks {
131 mac.update(chunk);
132 }
133 mac.verify_slice(signature)
134 .map_err(|_| CryptoError::MacVerificationFailed)
135 }};
136 }
137
138 match alg {
139 TpmAlgId::Sha256 => verify_hmac!(Sha256),
140 TpmAlgId::Sha384 => verify_hmac!(Sha384),
141 TpmAlgId::Sha512 => verify_hmac!(Sha512),
142 _ => Err(CryptoError::InvalidHashAlgorithm),
143 }
144}
145
146pub fn crypto_kdfa(
152 auth_hash: TpmAlgId,
153 hmac_key: &[u8],
154 label: &str,
155 context_a: &[u8],
156 context_b: &[u8],
157 key_bits: u16,
158) -> Result<Vec<u8>, CryptoError> {
159 let mut key_stream = Vec::new();
160 let key_bytes = (key_bits as usize).div_ceil(8);
161 let label_bytes = {
162 let mut bytes = label.as_bytes().to_vec();
163 bytes.push(0);
164 bytes
165 };
166
167 let mut counter: u32 = 1;
168 while key_stream.len() < key_bytes {
169 let counter_bytes = counter.to_be_bytes();
170 let key_bits_bytes = u32::from(key_bits).to_be_bytes();
171 let hmac_payload = [
172 counter_bytes.as_slice(),
173 label_bytes.as_slice(),
174 context_a,
175 context_b,
176 key_bits_bytes.as_slice(),
177 ];
178
179 let result = crypto_hmac(auth_hash, hmac_key, &hmac_payload)?;
180 let remaining = key_bytes - key_stream.len();
181 let to_take = remaining.min(result.len());
182 key_stream.extend_from_slice(&result[..to_take]);
183
184 counter += 1;
185 }
186
187 Ok(key_stream)
188}
189
190pub fn crypto_kdfe(
196 hash_alg: TpmAlgId,
197 z: &[u8],
198 label: &str,
199 context_u: &[u8],
200 context_v: &[u8],
201 key_bits: u16,
202) -> Result<Vec<u8>, CryptoError> {
203 let mut key_stream = Vec::new();
204 let key_bytes = (key_bits as usize).div_ceil(8);
205 let mut label_bytes = label.as_bytes().to_vec();
206 if label_bytes.last() != Some(&0) {
207 label_bytes.push(0);
208 }
209
210 let other_info = [label_bytes.as_slice(), context_u, context_v].concat();
211
212 let mut counter: u32 = 1;
213 while key_stream.len() < key_bytes {
214 let counter_bytes = counter.to_be_bytes();
215 let digest_payload = [&counter_bytes, z, &other_info];
216
217 let result = crypto_digest(hash_alg, &digest_payload)?;
218 let remaining = key_bytes - key_stream.len();
219 let to_take = remaining.min(result.len());
220 key_stream.extend_from_slice(&result[..to_take]);
221
222 counter += 1;
223 }
224
225 Ok(key_stream)
226}
227
228fn dispatch_rsa_oaep_encrypt(
230 key: &rsa::RsaPublicKey,
231 rng: &mut (impl CryptoRng + RngCore),
232 name_alg: TpmAlgId,
233 label: &str,
234 data: &[u8],
235) -> Result<Vec<u8>, CryptoError> {
236 let result = match name_alg {
237 TpmAlgId::Sha1 => key.encrypt(rng, Oaep::new_with_label::<Sha1, _>(label), data),
238 TpmAlgId::Sha256 => key.encrypt(rng, Oaep::new_with_label::<Sha256, _>(label), data),
239 TpmAlgId::Sha384 => key.encrypt(rng, Oaep::new_with_label::<Sha384, _>(label), data),
240 TpmAlgId::Sha512 => key.encrypt(rng, Oaep::new_with_label::<Sha512, _>(label), data),
241 _ => return Err(CryptoError::InvalidScheme),
242 };
243 result.map_err(|e| CryptoError::RsaOperationFailed(e.to_string()))
244}
245
246pub fn protect_seed_with_rsa(
254 parent_public: &TpmtPublic,
255 seed: &[u8],
256 rng: &mut (impl RngCore + CryptoRng),
257) -> Result<Tpm2bEncryptedSecret, CryptoError> {
258 let n = match &parent_public.unique {
259 TpmuPublicId::Rsa(data) => Ok(data.as_ref()),
260 _ => Err(CryptoError::InvalidKey),
261 }?;
262 let e_raw = match &parent_public.parameters {
263 TpmuPublicParms::Rsa(params) => Ok(params.exponent),
264 _ => Err(CryptoError::InvalidKey),
265 }?;
266 let e = if e_raw == 0 { 65537 } else { e_raw };
267 let rsa_pub_key = rsa::RsaPublicKey::new(
268 rsa::BigUint::from_bytes_be(n),
269 rsa::BigUint::from_u32(e).ok_or_else(|| {
270 CryptoError::ValueConversionFailed("invalid RSA exponent".to_string())
271 })?,
272 )
273 .map_err(|e| CryptoError::RsaOperationFailed(e.to_string()))?;
274
275 let label = "DUPLICATE\0";
276
277 let encrypted_seed =
278 dispatch_rsa_oaep_encrypt(&rsa_pub_key, rng, parent_public.name_alg, label, seed)?;
279
280 Tpm2bEncryptedSecret::try_from(encrypted_seed.as_slice())
281 .map_err(|e| CryptoError::ValueConversionFailed(e.to_string()))
282}
283
284pub fn derive_seed_with_ecc(
290 parent_public: &TpmtPublic,
291 rng: &mut (impl RngCore + CryptoRng),
292) -> Result<(Vec<u8>, TpmsEccPoint), CryptoError> {
293 let (parent_point, curve_id) = match (&parent_public.unique, &parent_public.parameters) {
294 (TpmuPublicId::Ecc(point), TpmuPublicParms::Ecc(params)) => Ok((point, params.curve_id)),
295 _ => Err(CryptoError::InvalidKey),
296 }?;
297
298 match curve_id {
299 TpmEccCurve::NistP256 => crypto_ecdh_p256(parent_point, parent_public.name_alg, rng),
300 TpmEccCurve::NistP384 => crypto_ecdh_p384(parent_point, parent_public.name_alg, rng),
301 TpmEccCurve::NistP521 => crypto_ecdh_p521(parent_point, parent_public.name_alg, rng),
302 _ => Err(CryptoError::UnsupportedCurve),
303 }
304}
305
306macro_rules! ecdh {
307 (
308 $vis:vis $fn_name:ident,
309 $pk_ty:ty, $sk_ty:ty, $affine_ty:ty, $dh_fn:path, $encoded_point_ty:ty
310 ) => {
311 #[allow(clippy::similar_names, clippy::missing_errors_doc)]
312 $vis fn $fn_name(
313 parent_point: &TpmsEccPoint,
314 name_alg: TpmAlgId,
315 rng: &mut (impl RngCore + CryptoRng),
316 ) -> Result<(Vec<u8>, TpmsEccPoint), CryptoError> {
317 let encoded_point = <$encoded_point_ty>::from_affine_coordinates(
318 parent_point.x.as_ref().into(),
319 parent_point.y.as_ref().into(),
320 false,
321 );
322 let affine_point_opt: Option<$affine_ty> =
323 <$affine_ty>::from_encoded_point(&encoded_point).into();
324 let affine_point = affine_point_opt.ok_or(CryptoError::InvalidEccPoint)?;
325
326 if affine_point.is_identity().into() {
327 return Err(CryptoError::InvalidEccPoint);
328 }
329
330 let parent_pk =
331 <$pk_ty>::from_affine(affine_point).map_err(|_| CryptoError::InvalidEccPoint)?;
332
333 let ephemeral_sk = <$sk_ty>::random(rng);
334 let ephemeral_pk_bytes_encoded = ephemeral_sk.public_key().to_encoded_point(false);
335 let ephemeral_pk_bytes = ephemeral_pk_bytes_encoded.as_bytes();
336 if ephemeral_pk_bytes.is_empty() || ephemeral_pk_bytes[0] != UNCOMPRESSED_POINT_TAG {
337 return Err(CryptoError::InvalidEccPoint);
338 }
339 let coord_len = (ephemeral_pk_bytes.len() - 1) / 2;
340 let x = &ephemeral_pk_bytes[1..=coord_len];
341 let y = &ephemeral_pk_bytes[1 + coord_len..];
342
343 let context_u = x;
344 let context_v = parent_point.x.as_ref();
345
346 let shared_secret = $dh_fn(ephemeral_sk.to_nonzero_scalar(), parent_pk.as_affine());
347 let z = shared_secret.raw_secret_bytes();
348 let seed_bits =
349 u16::try_from(tpm_hash_size(&name_alg).ok_or(CryptoError::InvalidHashAlgorithm)? * 8)
350 .map_err(|_| CryptoError::InvalidKey)?;
351 let seed =
352 crypto_kdfe(name_alg, &z, KDF_LABEL_DUPLICATE, context_u, context_v, seed_bits)?;
353
354 let ephemeral_point = TpmsEccPoint {
355 x: Tpm2bEccParameter::try_from(x)
356 .map_err(|e| CryptoError::ValueConversionFailed(e.to_string()))?,
357 y: Tpm2bEccParameter::try_from(y)
358 .map_err(|e| CryptoError::ValueConversionFailed(e.to_string()))?,
359 };
360
361 Ok((seed, ephemeral_point))
362 }
363 };
364}
365
366ecdh!(
367 pub crypto_ecdh_p256,
368 p256::PublicKey,
369 p256::SecretKey,
370 p256::AffinePoint,
371 p256::ecdh::diffie_hellman,
372 p256::EncodedPoint
373);
374
375ecdh!(
376 pub crypto_ecdh_p384,
377 p384::PublicKey,
378 p384::SecretKey,
379 p384::AffinePoint,
380 p384::ecdh::diffie_hellman,
381 p384::EncodedPoint
382);
383
384ecdh!(
385 pub crypto_ecdh_p521,
386 p521::PublicKey,
387 p521::SecretKey,
388 p521::AffinePoint,
389 p521::ecdh::diffie_hellman,
390 p521::EncodedPoint
391);
392
393pub fn crypto_make_name(public: &TpmtPublic) -> Result<Tpm2bName, CryptoError> {
399 let mut name_buf = Vec::new();
400 let name_alg = public.name_alg;
401 name_buf.extend_from_slice(&(name_alg as u16).to_be_bytes());
402 let public_area_bytes = from_tpm_object_to_vec(public)
403 .map_err(|e| CryptoError::ValueConversionFailed(e.to_string()))?;
404 let digest = crypto_digest(name_alg, &[&public_area_bytes])?;
405 name_buf.extend_from_slice(&digest);
406 Tpm2bName::try_from(name_buf.as_slice()).map_err(Into::into)
407}