cli/policy/
software.rs

1// SPDX-License-Identifier: GPL-3-0-or-later
2// Copyright (c) 2025 Opinsys Oy
3
4//! A pure software implementation of a policy session for dry-run calculations.
5
6use super::{PolicyError, PolicySession};
7use crate::{
8    convert::from_tpm_object_to_vec,
9    crypto,
10    device::{Device, DeviceError},
11};
12use tpm2_protocol::{
13    data::{Tpm2bDigest, Tpm2bName, Tpm2bNonce, TpmAlgId, TpmCc, TpmlDigest, TpmlPcrSelection},
14    tpm_hash_size,
15};
16
17/// Updates a policy digest with a new command, mimicking the TPM's internal
18/// hashing. This can be shared between the software session and the mock TPM.
19///
20/// # Errors
21///
22/// Returns an error if the hash algorithm is unsupported.
23pub fn update_policy_digest(
24    current_digest: &mut Tpm2bDigest,
25    hash_alg: TpmAlgId,
26    cc: TpmCc,
27    params: &[&[u8]],
28) -> Result<(), PolicyError> {
29    let cc_bytes = (cc as u32).to_be_bytes();
30    let mut chunks: Vec<&[u8]> = Vec::with_capacity(2 + params.len());
31    chunks.push(current_digest.as_ref());
32    chunks.push(&cc_bytes);
33    chunks.extend(params.iter());
34
35    let new_digest_bytes = crypto::crypto_digest(hash_alg, &chunks)?;
36    *current_digest =
37        Tpm2bDigest::try_from(new_digest_bytes.as_slice()).map_err(DeviceError::from)?;
38    Ok(())
39}
40
41/// A session that simulates TPM policy digest calculations in software.
42pub struct SoftwarePolicySession<'a> {
43    digest: Tpm2bDigest,
44    hash_alg: TpmAlgId,
45    digest_size: usize,
46    device: &'a mut Device,
47}
48
49impl<'a> SoftwarePolicySession<'a> {
50    /// Creates a new software policy session.
51    ///
52    /// # Errors
53    ///
54    /// Returns `PolicyError::InvalidAlgorithm` if the hash algorithm is not supported.
55    pub fn new(hash_alg: TpmAlgId, device: &'a mut Device) -> Result<Self, PolicyError> {
56        let digest_size =
57            tpm_hash_size(&hash_alg).ok_or(PolicyError::InvalidAlgorithm(hash_alg))?;
58        let digest =
59            Tpm2bDigest::try_from(vec![0; digest_size].as_slice()).map_err(DeviceError::from)?;
60        Ok(Self {
61            digest,
62            hash_alg,
63            digest_size,
64            device,
65        })
66    }
67}
68
69impl PolicySession for SoftwarePolicySession<'_> {
70    fn device(&mut self) -> &mut Device {
71        self.device
72    }
73
74    fn policy_pcr(
75        &mut self,
76        pcr_digest: &Tpm2bDigest,
77        pcrs: TpmlPcrSelection,
78    ) -> Result<(), PolicyError> {
79        let pcrs_bytes = from_tpm_object_to_vec(&pcrs).map_err(DeviceError::from)?;
80        update_policy_digest(
81            &mut self.digest,
82            self.hash_alg,
83            TpmCc::PolicyPcr,
84            &[&pcrs_bytes, pcr_digest.as_ref()],
85        )
86    }
87
88    fn policy_or(&mut self, p_hash_list: &TpmlDigest) -> Result<(), PolicyError> {
89        let digests_as_bytes: Vec<u8> = p_hash_list
90            .iter()
91            .flat_map(std::convert::AsRef::as_ref)
92            .copied()
93            .collect();
94
95        self.digest = Tpm2bDigest::try_from(vec![0; self.digest_size].as_slice())
96            .map_err(DeviceError::from)?;
97
98        update_policy_digest(
99            &mut self.digest,
100            self.hash_alg,
101            TpmCc::PolicyOR,
102            &[&digests_as_bytes],
103        )
104    }
105
106    fn policy_secret(
107        &mut self,
108        _auth_handle: u32,
109        auth_handle_name: &Tpm2bName,
110        _password: Option<&[u8]>,
111        _cp_hash: Option<Tpm2bDigest>,
112    ) -> Result<(), PolicyError> {
113        let command_code = TpmCc::PolicySecret;
114        let policy_ref = Tpm2bNonce::default();
115        let cc_bytes = (command_code as u32).to_be_bytes();
116
117        let intermediate_digest_bytes = crypto::crypto_digest(
118            self.hash_alg,
119            &[self.digest.as_ref(), &cc_bytes, auth_handle_name.as_ref()],
120        )?;
121
122        let final_digest_bytes = crypto::crypto_digest(
123            self.hash_alg,
124            &[&intermediate_digest_bytes, policy_ref.as_ref()],
125        )?;
126
127        self.digest =
128            Tpm2bDigest::try_from(final_digest_bytes.as_slice()).map_err(DeviceError::from)?;
129        Ok(())
130    }
131
132    fn get_digest(&mut self) -> Result<Tpm2bDigest, PolicyError> {
133        Ok(self.digest)
134    }
135
136    fn hash_alg(&self) -> TpmAlgId {
137        self.hash_alg
138    }
139}