tss_keyfile/tpmkey.rs
1use alloc::vec::Vec;
2
3#[cfg(feature = "pem")]
4use alloc::string::String;
5
6use hex::ToHex;
7use rasn::prelude::*;
8
9/// A TPM loadable key, to be loaded with `TPM2_Load`
10pub const OID_TPMKEY_LOADABLE: &Oid = Oid::const_new(&[2, 23, 133, 10, 1, 3]);
11
12/// A TPM importable key, to be imported with `TPM2_Import`
13pub const OID_TPMKEY_IMPORTABLE: &Oid = Oid::const_new(&[2, 23, 133, 10, 1, 4]);
14
15/// A sealed data key, to be extracted with `TPM2_Unseal`
16pub const OID_TPMKEY_SEALED: &Oid = Oid::const_new(&[2, 23, 133, 10, 1, 5]);
17
18/// TPM Key Type (OID encoding)
19#[derive(AsnType, Clone, Debug, Decode, Encode, PartialEq, Eq, Hash)]
20#[rasn(delegate)]
21pub struct TPMKeyType(ObjectIdentifier);
22
23impl TPMKeyType {
24 pub fn new_loadable() -> Self {
25 Self(ObjectIdentifier::from(OID_TPMKEY_LOADABLE))
26 }
27
28 pub fn new_importable() -> Self {
29 Self(ObjectIdentifier::from(OID_TPMKEY_IMPORTABLE))
30 }
31
32 pub fn new_sealed() -> Self {
33 Self(ObjectIdentifier::from(OID_TPMKEY_SEALED))
34 }
35
36 pub fn validate(&self) -> bool {
37 // validate OID
38 self.0 == OID_TPMKEY_LOADABLE
39 || self.0 == OID_TPMKEY_IMPORTABLE
40 || self.0 == OID_TPMKEY_SEALED
41 }
42}
43
44impl AsRef<ObjectIdentifier> for TPMKeyType {
45 fn as_ref(&self) -> &ObjectIdentifier {
46 &self.0
47 }
48}
49
50impl core::fmt::Display for TPMKeyType {
51 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
52 let typestr = if self.0 == OID_TPMKEY_LOADABLE {
53 "Loadable Key"
54 } else if self.0 == OID_TPMKEY_IMPORTABLE {
55 "Importable Key"
56 } else if self.0 == OID_TPMKEY_SEALED {
57 "Sealed Data"
58 } else {
59 "Unknown"
60 };
61 write!(f, "{typestr} (OID:{})", &self.0)
62 }
63}
64
65#[derive(AsnType, Clone, Debug, Decode, Encode, PartialEq, Eq, Hash)]
66pub struct TPMPolicy {
67 /// This is the integer representation of the TPM command code for the policy statement.
68 #[rasn(tag(explicit(0)), default)]
69 pub command_code: u32,
70
71 /// This is a binary string representing a fully marshalled, TPM ordered, command body
72 /// for the TPM policy command. Therefore to send the command, the implementation simply
73 /// marshals the command code and appends this octet string as the body.
74 ///
75 /// Commands which have no body, such as TPM2_AuthVal, MUST be specified as a zero length
76 /// OCTET STRING
77 ///
78 /// Note that there are some commands for which the simple body of the TPM policy command
79 /// does not provide enough information to execute the policy command. A classic example
80 /// is TPM2_PolicyAuthorize, whose body consists of a key name, a policyRef nonce and a
81 /// signature. However, the implementation needs to know the actual key, not just the name,
82 /// to implement the policy
83 #[rasn(tag(explicit(1)), default)]
84 pub command_policy: OctetString,
85}
86
87impl core::fmt::Display for TPMPolicy {
88 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
89 write!(
90 f,
91 "TPMPolicy {{ command_code: 0x{:08X}, command_policy: {} bytes }}",
92 self.command_code,
93 self.command_policy.len()
94 )
95 }
96}
97
98#[derive(AsnType, Clone, Debug, Decode, Encode, PartialEq, Eq, Hash)]
99pub struct TPMAuthPolicy {
100 /// An optional string name for the current policy which is only used for display purposes,
101 /// MAY be used as a user visible mnemonic for the actual policy.
102 #[rasn(tag(explicit(0)), default)]
103 pub name: Option<Utf8String>,
104
105 /// A sequence of TPMPolicy statements which MUST end with a PolicyAuthorize statement whose
106 /// signature is over the hash of the current policy excluding the PolicyAuthorize statement
107 /// and the nonce specified at the beginning of the TPMKey policy.
108 /// There MUST be no other TPM2_PolicyAuthorize statements in the intermediate policy steps.
109 /// There MAY be an initial TPM2_PolicyAuthorize statement containing a different public key
110 /// (because this causes complexity building signed policy chains, implementations MAY choose
111 /// to allow only a single policy signing key, in which case there MAY NOT be an initial
112 /// TPM2_PolicyAuthorize statement).
113 #[rasn(tag(explicit(1)), default)]
114 pub policy: SequenceOf<TPMPolicy>,
115}
116
117impl core::fmt::Display for TPMAuthPolicy {
118 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
119 write!(f, "TPMAuthPolicy {{")?;
120 if let Some(name) = &self.name {
121 write!(f, " name: \"{}\",", name)?;
122 }
123 write!(f, " policy: {} entries }}", self.policy.len())
124 }
125}
126
127/// The design of the TPMkey ASN.1 format is that it should have a distinguishing OID
128/// at the beginning so the DER form of the key can be easily recognized.
129/// In PEM form, the key MUST have the following PEM guards:
130/// "-----BEGIN TSS2 PRIVATE KEY-----" and "-----END TSS2 PRIVATE KEY-----"
131///
132/// All additional information that may be needed to load the key is specified as
133/// optional explicit elements, which can be extended by later specifications,
134/// which is why the TPMkey is not versioned.
135#[derive(AsnType, Clone, Debug, Decode, Encode, PartialEq, Eq, Hash)]
136pub struct TPMKey {
137 /// A unique OID specifying the key type:
138 /// - TPMKEY_LOADABLE
139 /// - TPMKEY_IMPORTABLE
140 /// - TPMKEY_SEALED
141 pub r#type: TPMKeyType,
142
143 /// An implementation needs to know as it formulates the TPM2_Load/Import/Unseal
144 /// command whether it must also send down an authorization. So this parameter
145 /// gives that indication.
146 /// emptyAuth MUST be true if authorization is NOT required and MUST be either
147 /// false or absent if authorization is required.
148 ///
149 /// Since this element has three states (one representing true and two representing false)
150 /// it is RECOMMENDED that implementations emitting TPMkey representations use absence of
151 /// the tag to represent false. However, implementations reading TPMKey MUST be able to
152 /// process all three possible states.
153 #[rasn(tag(explicit(0)), default)]
154 pub empty_auth: Option<bool>,
155
156 /// This MUST be present if the TPM key has a policy hash because it describes to the
157 /// implementation how to construct the policy
158 #[rasn(tag(explicit(1)), default)]
159 pub policy: Option<SequenceOf<TPMPolicy>>,
160
161 /// secret contains the additional cryptographic secret used to specify the
162 /// outer wrapping of an importable object. For keys, it MUST be present for key
163 /// type id-importablekey and MUST NOT be present for key type id-loadablekey.
164 /// For sealed data objects of type id-sealedkey, it MAY be present and if present
165 /// indicates the object is importable.
166 ///
167 /// Importable objects (designed to be processed by TPM2_Import) MUST have an
168 /// unencrypted inner wrapper (symmetricAlg MUST be TPM_ALG_NULL and encryptionKey
169 /// MUST be empty) and an outer wrapper encrypted to the parent key using inSymSeed.
170 /// The secret parameter is the fully marshalled TPM2B_ENCRYPTED_SECRET form of inSymSeed.
171 #[rasn(tag(explicit(2)), default)]
172 pub secret: Option<OctetString>,
173
174 /// This SHOULD be present if the TPMkey policy contains a TPM2_PolicyAuthorize
175 /// statement because it contains signed policies that could be used to satisfy
176 /// the TPM key policy.
177 /// If the TPM key has no policy hash then this MUST NOT be present.
178 #[rasn(tag(explicit(3)), default)]
179 pub auth_policy: Option<SequenceOf<TPMAuthPolicy>>,
180
181 /// An optional string description for the key which is only used for display purposes,
182 /// MAY be used as a user visible mnemonic for the key.
183 #[rasn(tag(explicit(4)), default)]
184 pub description: Option<Utf8String>,
185
186 /// This MUST be present and true if the parent is a permanent handle (MSO 0x40)
187 /// and RSA 2048 is used for the primary key.
188 /// If the parent is not a permanent handle then this MUST NOT be present.
189 /// If the parent is a permanent handle and if P-256 is used for the primary then
190 /// this MUST NOT be present. Given that P-256 primary keys are easier to generate,
191 /// implementations SHOULD NOT set this flag.
192 #[rasn(tag(explicit(5)), default)]
193 pub rsa_parent: Option<bool>,
194
195 /// This MUST be present for all keys and specifies the handle of the parent key.
196 /// The parent key SHOULD be either a persistent handle (MSO 0x81) or a permanent
197 /// handle (MSO 0x40).
198 /// Since volatile handle numbering can change unexpectedly depending on key load order,
199 /// the parent SHOULD NOT be a volatile handle (MSO 0x80).
200 /// The parent MUST NOT have any other MSO.
201 pub parent: u32,
202
203 /// This MUST be present and MUST correspond to the fully marshalled
204 /// TPM2B_PUBLIC structure of the TPM Key.
205 pub pubkey: OctetString,
206
207 /// This MUST be present and MUST correspond to the fully marshalled
208 /// TPM2B_PRIVATE structure of the TPM Key.
209 /// For importable keys, this must be the duplicate parameter that would be input to TPM2_Import.
210 pub privkey: OctetString,
211}
212
213impl core::fmt::Display for TPMKey {
214 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
215 writeln!(f, "TPMKey {{")?;
216 writeln!(f, " type: \"{}\"", self.r#type)?;
217 if let Some(empty_auth) = &self.empty_auth {
218 writeln!(f, " empty_auth: \"{}\"", empty_auth)?;
219 }
220 if let Some(policy) = &self.policy {
221 writeln!(f, " policy: {} entries", policy.len())?;
222 }
223 if let Some(secret) = &self.secret {
224 writeln!(f, " secret: {} bytes", secret.len())?;
225 }
226 if let Some(auth_policy) = &self.auth_policy {
227 writeln!(f, " auth_policy: {} entries", auth_policy.len())?;
228 }
229 if let Some(description) = &self.description {
230 writeln!(f, " description: \"{}\"", description)?;
231 }
232 if let Some(rsa_parent) = &self.rsa_parent {
233 writeln!(f, " rsa_parent: \"{}\"", rsa_parent)?;
234 }
235 writeln!(f, " parent: \"0x{:08X}\"", self.parent)?;
236 write!(f, " pubkey: \"")?;
237 for b in self.pubkey.iter() {
238 write!(f, "{:02X}", b)?;
239 }
240 writeln!(f, "\"")?;
241 write!(f, " privkey: \"")?;
242 for b in self.privkey.iter() {
243 write!(f, "{:02X}", b)?;
244 }
245 writeln!(f, "\"")?;
246 write!(f, "}}")
247 }
248}
249
250#[cfg(feature = "pem")]
251impl TPMKey {
252 pub fn try_from_pem(pem: &[u8]) -> Result<TPMKey, crate::Error> {
253 let der = crate::checked_pem_to_der(pem)?;
254 TPMKey::try_from_der(&der)
255 }
256
257 pub fn try_to_pem(&self) -> Result<String, crate::Error> {
258 let der = self.try_to_der()?;
259
260 let pem =
261 pem_rfc7468::encode_string("TSS2 PRIVATE KEY", pem_rfc7468::LineEnding::LF, &der)?;
262
263 Ok(pem)
264 }
265}
266
267impl TPMKey {
268 pub fn try_from_der(der: &[u8]) -> Result<TPMKey, crate::Error> {
269 let tpmkey: TPMKey = rasn::der::decode(der)?;
270 tpmkey.validate()?;
271 Ok(tpmkey)
272 }
273
274 pub fn try_to_der(&self) -> Result<Vec<u8>, crate::Error> {
275 Ok(rasn::der::encode(self)?)
276 }
277
278 pub fn new_loadable(parent: u32, pubkey: &[u8], privkey: &[u8]) -> Self {
279 Self {
280 r#type: TPMKeyType::new_loadable(),
281 parent,
282 pubkey: pubkey.into(),
283 privkey: privkey.into(),
284 empty_auth: None,
285 policy: None,
286 secret: None,
287 auth_policy: None,
288 description: None,
289 rsa_parent: None,
290 }
291 }
292
293 pub fn new_importable(parent: u32, pubkey: &[u8], privkey: &[u8]) -> Self {
294 Self {
295 r#type: TPMKeyType::new_importable(),
296 parent,
297 pubkey: pubkey.into(),
298 privkey: privkey.into(),
299 empty_auth: None,
300 policy: None,
301 secret: None,
302 auth_policy: None,
303 description: None,
304 rsa_parent: None,
305 }
306 }
307
308 pub fn new_sealed(parent: u32, pubkey: &[u8], privkey: &[u8]) -> Self {
309 Self {
310 r#type: TPMKeyType::new_sealed(),
311 parent,
312 pubkey: pubkey.into(),
313 privkey: privkey.into(),
314 empty_auth: None,
315 policy: None,
316 secret: None,
317 auth_policy: None,
318 description: None,
319 rsa_parent: None,
320 }
321 }
322
323 pub fn is_loadable(&self) -> bool {
324 self.r#type.0 == OID_TPMKEY_LOADABLE
325 }
326
327 pub fn is_importable(&self) -> bool {
328 self.r#type.0 == OID_TPMKEY_IMPORTABLE
329 }
330
331 pub fn is_sealed(&self) -> bool {
332 self.r#type.0 == OID_TPMKEY_SEALED
333 }
334
335 pub fn requires_auth(&self) -> bool {
336 !self.empty_auth.unwrap_or(false)
337 }
338
339 pub fn validate(&self) -> Result<(), crate::Error> {
340 // validate OID
341 if !self.r#type.validate() {
342 return Err(crate::Error::NoTss2PrivateKey);
343 };
344
345 // validate parent handle is permanent, not unknown
346 // transient is discouraged but allowed
347 let mso = (self.parent >> 24) & 0xFF;
348 if mso != 0x40 && mso != 0x81 && mso != 0x80 {
349 return Err(crate::Error::InvalidParentHandle(
350 self.parent.to_be_bytes().encode_hex(),
351 ));
352 };
353
354 // Validate secret field requirements based on key type
355 if self.is_loadable() && self.secret.is_some() {
356 return Err(crate::Error::UnexpectedSecret);
357 }
358
359 if self.is_importable() && self.secret.is_none() {
360 return Err(crate::Error::MissingSecret);
361 }
362
363 Ok(())
364 }
365}