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