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