1#![allow(clippy::no_effect_underscore_binding)]
6
7use super::{Alg, ExternalKey, KeyError, Tpm2shAlgId};
8use crate::{
9 auth::Auth,
10 crypto::{
11 crypto_hash_size, crypto_hmac, crypto_kdfa, crypto_make_name, derive_seed_with_ecc,
12 protect_seed_with_rsa, KDF_LABEL_INTEGRITY, KDF_LABEL_STORAGE,
13 },
14 device::{Device, DeviceError},
15 job::{Job, JobError},
16 template,
17 vtpm::VtpmError,
18 write_object,
19};
20
21use aes::Aes128;
22use cfb_mode::Encryptor;
23use cipher::{AsyncStreamCipher, KeyIvInit};
24use pem::Pem;
25use rand::{CryptoRng, RngCore};
26use rasn::{
27 prelude::ObjectIdentifier,
28 types::{OctetString, Utf8String},
29 AsnType, Decode, Decoder, Encode, Encoder,
30};
31use tpm2_protocol::data::{TpmAlgId, TpmRcBase, TpmtPublic};
32use tpm2_protocol::{
33 constant::TPM_MAX_COMMAND_SIZE,
34 data::{
35 Tpm2bAuth, Tpm2bData, Tpm2bDigest, Tpm2bEccParameter, Tpm2bEncryptedSecret, Tpm2bName,
36 Tpm2bPrivate, Tpm2bPrivateKeyRsa, Tpm2bPublic, Tpm2bSensitive, Tpm2bSensitiveCreate,
37 Tpm2bSensitiveData, Tpm2bSymKey, TpmCc, TpmaObject, TpmlPcrSelection, TpmsSensitiveCreate,
38 TpmtSensitive, TpmtSymDefObject, TpmuSensitiveComposite,
39 },
40 message::{TpmCreateCommand, TpmImportCommand},
41 TpmBuild, TpmError, TpmHandle, TpmParse, TpmWriter,
42};
43
44pub const OID_LOADABLE_KEY: ObjectIdentifier =
45 ObjectIdentifier::new_unchecked(std::borrow::Cow::Borrowed(&[2, 23, 133, 10, 1, 3]));
46pub const OID_IMPORTABLE_KEY: ObjectIdentifier =
47 ObjectIdentifier::new_unchecked(std::borrow::Cow::Borrowed(&[2, 23, 133, 10, 1, 4]));
48pub const OID_SEALED_DATA: ObjectIdentifier =
49 ObjectIdentifier::new_unchecked(std::borrow::Cow::Borrowed(&[2, 23, 133, 10, 1, 5]));
50
51pub struct TpmKeyTemplate<'a> {
53 pub alg_desc: &'a Alg,
54 pub sensitive_data: Tpm2bSensitiveData,
55 pub key_type_oid: ObjectIdentifier,
56}
57
58#[derive(AsnType, Decode, Encode, Clone, Debug, Eq, PartialEq)]
60pub struct TpmPolicy {
61 #[rasn(tag(explicit(context, 0)))]
62 pub command_code: u32,
63 #[rasn(tag(explicit(context, 1)))]
64 pub command_policy: OctetString,
65}
66
67#[derive(AsnType, Decode, Encode, Clone, Debug, Eq, PartialEq)]
69pub struct TpmAuthPolicy {
70 #[rasn(tag(explicit(context, 0)))]
71 pub name: Option<Utf8String>,
72 #[rasn(tag(explicit(context, 1)))]
73 pub policy: Vec<TpmPolicy>,
74}
75
76#[derive(AsnType, Decode, Encode, Clone, Debug, Eq, PartialEq)]
78pub struct TpmKey {
79 pub key_type: ObjectIdentifier,
80 #[rasn(tag(explicit(context, 0)))]
81 pub empty_auth: Option<bool>,
82 #[rasn(tag(explicit(context, 1)))]
83 pub policy: Option<Vec<TpmPolicy>>,
84 #[rasn(tag(explicit(context, 2)))]
85 pub secret: Option<OctetString>,
86 #[rasn(tag(explicit(context, 3)))]
87 pub auth_policy: Option<Vec<TpmAuthPolicy>>,
88 #[rasn(tag(explicit(context, 4)))]
89 pub description: Option<Utf8String>,
90 #[rasn(tag(explicit(context, 5)))]
91 pub rsa_parent: Option<bool>,
92 #[rasn(tag(explicit(context, 6)))]
93 pub parent_pub_key: Option<OctetString>,
94 pub parent: u32,
95 pub pub_key: OctetString,
96 pub priv_key: OctetString,
97}
98
99impl TpmKey {
100 #[allow(clippy::too_many_arguments)]
106 pub fn new(
107 job: &mut Job,
108 device: &mut Device,
109 auth_list: &[Auth],
110 user_auth: Tpm2bAuth,
111 auth_policy: Tpm2bDigest,
112 object_attributes: TpmaObject,
113 parent_handle: TpmHandle,
114 template: &TpmKeyTemplate,
115 ) -> Result<Self, KeyError> {
116 let public_template =
117 template::build_public(template.alg_desc, auth_policy, object_attributes);
118
119 let create_cmd = TpmCreateCommand {
120 parent_handle: parent_handle.0.into(),
121 in_sensitive: Tpm2bSensitiveCreate {
122 inner: TpmsSensitiveCreate {
123 user_auth,
124 data: template.sensitive_data,
125 },
126 },
127 in_public: Tpm2bPublic {
128 inner: public_template,
129 },
130 outside_info: Tpm2bData::default(),
131 creation_pcr: TpmlPcrSelection::default(),
132 };
133
134 let handles = [parent_handle.0];
135 let (resp, _) = job
136 .execute(device, &create_cmd, &handles, auth_list)
137 .map_err(|e| {
138 if let JobError::Device(DeviceError::TpmRc(rc)) = &e {
139 if rc.base() == TpmRcBase::Type {
140 return KeyError::InvalidParent(parent_handle.0);
141 }
142 }
143 match e {
144 JobError::Device(d) => KeyError::Device(d),
145 JobError::Vtpm(
146 VtpmError::Auth(_)
147 | VtpmError::HandleNotFound(_, _)
148 | VtpmError::TrailingAuthorizations,
149 ) => KeyError::Device(DeviceError::TpmProtocol(TpmError::MalformedData)),
150 _ => KeyError::ValueConversionFailed(e.to_string()),
151 }
152 })?;
153
154 let create_resp = resp
155 .Create()
156 .map_err(|_| DeviceError::ResponseMismatch(TpmCc::Create))?;
157
158 let (parent_public, _) = device.read_public(parent_handle)?;
159 let parent_public_2b = Tpm2bPublic {
160 inner: parent_public,
161 };
162
163 Self::from_creation_data(
164 user_auth.is_empty(),
165 parent_handle,
166 &create_resp.out_public,
167 &create_resp.out_private,
168 &auth_policy,
169 template.key_type_oid.clone(),
170 &parent_public_2b,
171 )
172 }
173
174 #[allow(clippy::too_many_arguments)]
176 fn from_creation_data(
177 empty_auth: bool,
178 parent_handle: TpmHandle,
179 out_public: &Tpm2bPublic,
180 out_private: &Tpm2bPrivate,
181 policy_digest: &Tpm2bDigest,
182 key_type: ObjectIdentifier,
183 parent_public: &Tpm2bPublic,
184 ) -> Result<Self, KeyError> {
185 let policy = if policy_digest.is_empty() {
186 None
187 } else {
188 Some(vec![TpmPolicy {
189 command_code: 0,
190 command_policy: OctetString::copy_from_slice(policy_digest.as_ref()),
191 }])
192 };
193 Ok(Self {
194 key_type,
195 empty_auth: empty_auth.then_some(true),
196 policy,
197 secret: None,
198 auth_policy: None,
199 description: None,
200 rsa_parent: None,
201 parent_pub_key: Some(OctetString::copy_from_slice(
202 &write_object(parent_public).map_err(DeviceError::TpmProtocol)?,
203 )),
204 parent: parent_handle.0,
205 pub_key: OctetString::copy_from_slice(
206 &write_object(out_public).map_err(DeviceError::TpmProtocol)?,
207 ),
208 priv_key: OctetString::copy_from_slice(
209 &write_object(out_private).map_err(DeviceError::TpmProtocol)?,
210 ),
211 })
212 }
213
214 #[allow(clippy::too_many_arguments)]
232 pub fn from_external_key(
233 device: &mut Device,
234 job: &mut Job,
235 parent_handle: TpmHandle,
236 external_key: &ExternalKey,
237 rng: &mut (impl RngCore + CryptoRng),
238 handles: &[u32],
239 auth_list: &[Auth],
240 ) -> Result<Self, KeyError> {
241 let (parent_public, parent_name) = match device.read_public(parent_handle) {
242 Ok(result) => result,
243 Err(DeviceError::Io(e)) if e.kind() == std::io::ErrorKind::InvalidInput => {
244 return Err(KeyError::InvalidParent(parent_handle.0));
245 }
246 Err(e) => return Err(e.into()),
247 };
248 let parent_name_alg = parent_public.name_alg;
249
250 let public = external_key.to_public(parent_name_alg)?;
251 let object_name = crypto_make_name(&public)?;
252 let sensitive_blob = external_key.sensitive_blob();
253
254 let (duplicate, in_sym_seed, encryption_key) = create_import_blob(
255 &parent_public,
256 &public,
257 &sensitive_blob,
258 &parent_name,
259 &object_name,
260 rng,
261 )?;
262
263 let import_cmd = TpmImportCommand {
264 parent_handle: parent_handle.0.into(),
265 encryption_key,
266 object_public: Tpm2bPublic {
267 inner: public.clone(),
268 },
269 duplicate,
270 in_sym_seed,
271 symmetric_alg: TpmtSymDefObject::default(),
272 };
273
274 let (resp, _) = job
275 .execute(device, &import_cmd, handles, auth_list)
276 .map_err(|e| match e {
277 JobError::Device(d) => KeyError::Device(d),
278 JobError::Vtpm(
279 VtpmError::Auth(_)
280 | VtpmError::HandleNotFound(_, _)
281 | VtpmError::TrailingAuthorizations,
282 ) => KeyError::Device(DeviceError::TpmProtocol(TpmError::MalformedData)),
283 _ => KeyError::ValueConversionFailed(e.to_string()),
284 })?;
285
286 let import_resp = resp
287 .Import()
288 .map_err(|_| DeviceError::ResponseMismatch(TpmCc::Import))?;
289 let out_private = import_resp.out_private;
290
291 let parent_public_2b = Tpm2bPublic {
292 inner: parent_public,
293 };
294 let tpm_key = Self::from_creation_data(
295 true,
296 parent_handle,
297 &Tpm2bPublic { inner: public },
298 &out_private,
299 &Tpm2bDigest::default(),
300 OID_IMPORTABLE_KEY,
301 &parent_public_2b,
302 )?;
303
304 Ok(tpm_key)
305 }
306
307 pub fn public(&self) -> Result<Tpm2bPublic, KeyError> {
313 let (public, _) = Tpm2bPublic::parse(&self.pub_key).map_err(DeviceError::TpmProtocol)?;
314 Ok(public)
315 }
316
317 pub fn to_pem(&self) -> Result<String, KeyError> {
323 Ok(pem::encode(&Pem::new("TSS2 PRIVATE KEY", self.to_der()?)))
324 }
325
326 pub fn to_der(&self) -> Result<Vec<u8>, KeyError> {
332 rasn::der::encode(self).map_err(Into::into)
333 }
334
335 pub fn from_pem(pem_bytes: &[u8]) -> Result<Self, KeyError> {
341 let pem = pem::parse(pem_bytes)?;
342 if pem.tag() == "TSS2 PRIVATE KEY" {
343 Self::from_der(pem.contents())
344 } else {
345 Err(KeyError::UnsupportedPemTag(pem.tag().to_string()))
346 }
347 }
348
349 pub fn from_der(der_bytes: &[u8]) -> Result<Self, KeyError> {
355 rasn::der::decode(der_bytes).map_err(Into::into)
356 }
357}
358
359#[allow(clippy::too_many_arguments)]
368fn create_import_blob(
369 parent_public: &TpmtPublic,
370 object_public: &TpmtPublic,
371 private_bytes: &[u8],
372 _parent_name: &Tpm2bName,
373 object_name: &Tpm2bName,
374 rng: &mut (impl RngCore + CryptoRng),
375) -> Result<(Tpm2bPrivate, Tpm2bEncryptedSecret, Tpm2bData), KeyError> {
376 let parent_name_alg = parent_public.name_alg;
377 let parent_key_type = parent_public.object_type;
378
379 let (seed, in_sym_seed) = match parent_key_type {
380 TpmAlgId::Rsa => {
381 let seed_size = crypto_hash_size(parent_name_alg).ok_or(
382 KeyError::UnsupportedNameAlgorithm(Tpm2shAlgId(parent_name_alg)),
383 )? as usize;
384 let mut seed = vec![0u8; seed_size];
385 rng.fill_bytes(&mut seed);
386 let encrypted_seed = protect_seed_with_rsa(parent_public, &seed, rng)?;
387 (seed, encrypted_seed)
388 }
389 TpmAlgId::Ecc => {
390 let (derived_seed, ephemeral_point) = derive_seed_with_ecc(parent_public, rng)?;
391 let point_bytes = write_object(&ephemeral_point)?;
392 let secret = Tpm2bEncryptedSecret::try_from(point_bytes.as_slice())?;
393 (derived_seed, secret)
394 }
395 _ => {
396 return Err(KeyError::UnsupportedKeyAlgorithm(Tpm2shAlgId(
397 parent_key_type,
398 )))
399 }
400 };
401
402 let sym_key = crypto_kdfa(
403 parent_name_alg,
404 &seed,
405 KDF_LABEL_STORAGE,
406 object_name.as_ref(),
407 &[],
408 128,
409 )?;
410
411 let key_bits = crypto_hash_size(parent_name_alg).ok_or(KeyError::UnsupportedNameAlgorithm(
412 Tpm2shAlgId(parent_name_alg),
413 ))? * 8;
414 let key_bits =
415 u16::try_from(key_bits).map_err(|_| KeyError::InvalidRsaKeyBits(key_bits.to_string()))?;
416
417 let hmac_key = crypto_kdfa(
418 parent_name_alg,
419 &seed,
420 KDF_LABEL_INTEGRITY,
421 &[],
422 &[],
423 key_bits,
424 )?;
425
426 let object_key_type = object_public.object_type;
427 let sensitive_composite = match object_key_type {
428 TpmAlgId::Rsa => TpmuSensitiveComposite::Rsa(Tpm2bPrivateKeyRsa::try_from(private_bytes)?),
429 TpmAlgId::Ecc => TpmuSensitiveComposite::Ecc(Tpm2bEccParameter::try_from(private_bytes)?),
430 TpmAlgId::KeyedHash => {
431 TpmuSensitiveComposite::Bits(Tpm2bSensitiveData::try_from(private_bytes)?)
432 }
433 TpmAlgId::SymCipher => TpmuSensitiveComposite::Sym(Tpm2bSymKey::try_from(private_bytes)?),
434 _ => {
435 return Err(KeyError::UnsupportedKeyAlgorithm(Tpm2shAlgId(
436 object_key_type,
437 )))
438 }
439 };
440 let sensitive = TpmtSensitive {
441 sensitive_type: object_key_type,
442 auth_value: Tpm2bAuth::default(),
443 seed_value: Tpm2bDigest::default(),
444 sensitive: sensitive_composite,
445 };
446 let sensitive_tpm2b = Tpm2bSensitive::from(sensitive);
447 let mut enc_data = write_object(&sensitive_tpm2b)?;
448
449 let iv = [0u8; 16];
450 let cipher = Encryptor::<Aes128>::new(sym_key.as_slice().into(), &iv.into());
451 cipher.encrypt(&mut enc_data);
452
453 let final_mac = crypto_hmac(
454 parent_name_alg,
455 &hmac_key,
456 &[&enc_data, object_name.as_ref()],
457 )?;
458
459 let duplicate_blob = {
460 let mut duplicate_blob_buf = [0u8; TPM_MAX_COMMAND_SIZE];
461 let len = {
462 let mut writer = TpmWriter::new(&mut duplicate_blob_buf);
463 Tpm2bDigest::try_from(final_mac.as_slice())?.build(&mut writer)?;
464 writer.write_bytes(&enc_data)?;
465 writer.len()
466 };
467 duplicate_blob_buf[..len].to_vec()
468 };
469
470 Ok((
471 Tpm2bPrivate::try_from(duplicate_blob.as_slice())?,
472 in_sym_seed,
473 Tpm2bData::default(),
474 ))
475}