1use std::{
27 fmt::{self, Display},
28 fs::{create_dir, read_to_string, File},
29 hash::{DefaultHasher, Hash, Hasher},
30 io::{ErrorKind, Read, Write},
31 num::ParseIntError,
32 path::PathBuf,
33};
34
35const CONFIGFS_TSM_PATH: &str = "/sys/kernel/config/tsm/report";
37
38pub fn create_quote(input: [u8; 64]) -> Result<Vec<u8>, QuoteGenerationError> {
40 let quote_name = create_quote_name(&input);
41 let mut quote = OpenQuote::new("e_name)?;
42 quote.write_input(input)?;
43 quote.read_output()
44}
45
46pub fn create_quote_with_providers(
49 input: [u8; 64],
50 accepted_providers: Vec<&str>,
51) -> Result<Vec<u8>, QuoteGenerationError> {
52 let quote_name = create_quote_name(&input);
53 let mut quote = OpenQuote::new("e_name)?;
54 quote.check_provider(accepted_providers)?;
55 quote.write_input(input)?;
56 quote.read_output()
57}
58
59pub fn create_tdx_quote(input: [u8; 64]) -> Result<Vec<u8>, QuoteGenerationError> {
61 create_quote_with_providers(input, vec!["tdx_guest"])
62}
63
64pub struct OpenQuote {
66 path: PathBuf,
68 expected_generation: u32,
71}
72
73impl OpenQuote {
74 pub fn new(quote_name: &str) -> Result<Self, QuoteGenerationError> {
76 let mut quote_path = PathBuf::from(CONFIGFS_TSM_PATH);
77 quote_path.push(quote_name);
78
79 if let Err(error) = create_dir(quote_path.clone()) {
80 match error.kind() {
81 ErrorKind::AlreadyExists => {}
84 ErrorKind::NotFound => return Err(QuoteGenerationError::CannotFindTsmDir),
85 _ => return Err(QuoteGenerationError::IO(error)),
86 }
87 }
88
89 Ok(Self {
90 path: quote_path,
91 expected_generation: 0,
92 })
93 }
94
95 pub fn write_input(&mut self, input: [u8; 64]) -> Result<(), QuoteGenerationError> {
97 self.update_generation()?;
98 let mut inblob_path = self.path.clone();
99 inblob_path.push("inblob");
100 let mut inblob_file = File::create(inblob_path)?;
101 inblob_file.write_all(&input)?;
102
103 self.expected_generation += 1;
104 Ok(())
105 }
106
107 pub fn read_output(&self) -> Result<Vec<u8>, QuoteGenerationError> {
109 let mut outblob_path = self.path.clone();
110 outblob_path.push("outblob");
111 let mut outblob_file = File::open(outblob_path)?;
112 let mut output = Vec::new();
113 outblob_file.read_to_end(&mut output)?;
114
115 let actual = self.read_generation()?;
116 if self.expected_generation != actual {
117 return Err(QuoteGenerationError::Generation(
118 self.expected_generation,
119 actual,
120 ));
121 }
122 Ok(output)
123 }
124
125 pub fn read_generation(&self) -> Result<u32, QuoteGenerationError> {
127 let mut generation_path = self.path.clone();
128 generation_path.push("generation");
129 let mut current_generation = read_to_string(generation_path)?;
130 trim_newline(&mut current_generation);
131 Ok(current_generation.parse()?)
132 }
133
134 pub fn check_provider(&self, accepted_values: Vec<&str>) -> Result<(), QuoteGenerationError> {
136 let mut provider_path = self.path.clone();
137 provider_path.push("provider");
138 let mut provider = read_to_string(provider_path)?;
139 trim_newline(&mut provider);
140 if !accepted_values.contains(&provider.as_str()) {
141 return Err(QuoteGenerationError::BadProvider(provider));
142 }
143 Ok(())
144 }
145
146 fn update_generation(&mut self) -> Result<(), QuoteGenerationError> {
148 self.expected_generation = self.read_generation()?;
149 Ok(())
150 }
151}
152
153fn create_quote_name(input: &[u8]) -> String {
155 let mut s = DefaultHasher::new();
156 input.hash(&mut s);
157 let hash_bytes = s.finish().to_le_bytes();
158 bytes_to_hex(&hash_bytes)
159}
160
161fn bytes_to_hex(input: &[u8]) -> String {
162 input
163 .iter()
164 .map(|b| format!("{:02x}", b).to_string())
165 .collect::<Vec<String>>()
166 .join("")
167}
168
169fn trim_newline(input: &mut String) {
171 if input.ends_with('\n') {
172 input.pop();
173 if input.ends_with('\r') {
174 input.pop();
175 }
176 }
177}
178
179#[derive(Debug)]
181pub enum QuoteGenerationError {
182 Generation(u32, u32),
183 IO(std::io::Error),
184 ParseInt,
185 BadProvider(String),
186 CannotFindTsmDir,
187}
188
189impl Display for QuoteGenerationError {
190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 match self {
192 QuoteGenerationError::Generation(expected, actual) => f.write_str(&format!(
193 "Wrong generation number - possible conflict. Expected: {} Actual: {}",
194 expected, actual
195 )),
196 QuoteGenerationError::IO(error) => f.write_str(&error.to_string()),
197 QuoteGenerationError::ParseInt => {
198 f.write_str("Could not parse integer when reading generation value")
199 }
200 QuoteGenerationError::BadProvider(provider) => f.write_str(&format!(
201 "Quote has provider which is not allowed: {}",
202 provider
203 )),
204 QuoteGenerationError::CannotFindTsmDir => f.write_str(
205 "Cannot find configfs-tsm directory - maybe your hardware does not support it",
206 ),
207 }
208 }
209}
210
211impl From<std::io::Error> for QuoteGenerationError {
212 fn from(error: std::io::Error) -> QuoteGenerationError {
213 QuoteGenerationError::IO(error)
214 }
215}
216
217impl From<ParseIntError> for QuoteGenerationError {
218 fn from(_: ParseIntError) -> QuoteGenerationError {
219 QuoteGenerationError::ParseInt
220 }
221}