Skip to main content

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}