Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 6f4bc71

Browse files
committedFeb 26, 2025
Add capabilities_negotiation structures
This change aims to include those structures that will be required to communicate capabilities negotiation information for Keylime Push model Resolves: keylime#933 Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
1 parent 3b6c0ff commit 6f4bc71

File tree

3 files changed

+417
-0
lines changed

3 files changed

+417
-0
lines changed
 

‎keylime/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod ip_parser;
77
pub mod list_parser;
88
pub mod registrar_client;
99
pub mod serialization;
10+
pub mod structures;
1011
pub mod tpm;
1112
pub mod version;
1213

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,413 @@
1+
use serde::{Deserialize, Serialize};
2+
use serde_json::{from_value, to_value, Value as JsonValue};
3+
use std::convert::TryFrom;
4+
5+
// Define the structure for the AttestationRequest:
6+
#[derive(Serialize, Deserialize, Debug)]
7+
pub struct AttestationRequest {
8+
#[serde(rename(serialize = "data", deserialize = "data"))]
9+
pub data: RequestData,
10+
}
11+
#[derive(Serialize, Deserialize, Debug)]
12+
pub struct RequestData {
13+
#[serde(rename(serialize = "type", deserialize = "type"))]
14+
pub type_: String,
15+
pub attributes: Attributes,
16+
}
17+
18+
#[derive(Serialize, Deserialize, Debug)]
19+
pub struct Attributes {
20+
pub evidence_supported: Vec<EvidenceSupported>,
21+
pub boot_time: String,
22+
}
23+
24+
#[derive(Serialize, Deserialize, Debug)]
25+
pub struct EvidenceSupported {
26+
pub evidence_class: String,
27+
pub evidence_type: String,
28+
#[serde(skip_serializing_if = "Option::is_none")]
29+
pub agent_capabilities: Option<AgentCapabilities>,
30+
#[serde(skip_serializing_if = "Option::is_none")]
31+
pub version: Option<String>,
32+
}
33+
34+
#[derive(Serialize, Deserialize, Debug)]
35+
pub struct AgentCapabilities {
36+
pub spec_version: String,
37+
pub hash_algorithms: Vec<String>,
38+
pub signing_schemes: Vec<String>,
39+
pub attestation_keys: Vec<AttestationKeys>,
40+
}
41+
42+
#[derive(Serialize, Deserialize, Debug)]
43+
pub struct AttestationKeys {
44+
pub key_class: String,
45+
pub key_identifier: String,
46+
pub key_algorithm: String,
47+
pub public_hash: String,
48+
}
49+
50+
// Define the structure for the AttestationResponse:
51+
#[derive(Serialize, Deserialize, Debug)]
52+
pub struct AttestationResponse {
53+
#[serde(rename(serialize = "data", deserialize = "data"))]
54+
pub data: ResponseData,
55+
}
56+
57+
#[derive(Serialize, Deserialize, Debug)]
58+
pub struct ResponseData {
59+
#[serde(rename(serialize = "type", deserialize = "type"))]
60+
pub type_: String,
61+
pub attributes: ResponseAttributes,
62+
}
63+
64+
#[derive(Serialize, Deserialize, Debug)]
65+
pub struct ResponseAttributes {
66+
pub evidence_requested: Vec<EvidenceRequested>,
67+
pub boot_time: String,
68+
}
69+
70+
#[derive(Serialize, Deserialize, Debug)]
71+
pub struct EvidenceRequested {
72+
pub evidence_class: String,
73+
pub evidence_type: String,
74+
#[serde(skip_serializing_if = "Option::is_none")]
75+
pub chosen_parameters: Option<ChosenParameters>,
76+
#[serde(skip_serializing_if = "Option::is_none")]
77+
pub version: Option<String>,
78+
}
79+
80+
#[derive(Serialize, Deserialize, Debug, Clone)]
81+
pub struct TpmParameters {
82+
#[serde(skip_serializing_if = "Option::is_none")]
83+
pub nonce: Option<String>,
84+
#[serde(skip_serializing_if = "Option::is_none")]
85+
pub pcr_selection: Option<Vec<i32>>,
86+
#[serde(skip_serializing_if = "Option::is_none")]
87+
pub hash_algorithm: Option<String>,
88+
#[serde(skip_serializing_if = "Option::is_none")]
89+
pub signing_scheme: Option<String>,
90+
#[serde(skip_serializing_if = "Option::is_none")]
91+
pub attestation_key: Option<AttestationKey>,
92+
}
93+
94+
#[derive(Serialize, Deserialize, Debug, Clone)]
95+
pub struct StartingOffset {
96+
#[serde(skip_serializing_if = "Option::is_none")]
97+
pub starting_offset: Option<i32>,
98+
}
99+
100+
#[derive(Serialize, Deserialize, Debug, Clone)]
101+
#[serde(try_from = "JsonValue", into = "JsonValue")]
102+
pub enum ChosenParameters {
103+
Parameters(TpmParameters),
104+
Offset(StartingOffset),
105+
}
106+
107+
impl TryFrom<JsonValue> for ChosenParameters {
108+
type Error = String;
109+
110+
fn try_from(value: JsonValue) -> Result<Self, Self::Error> {
111+
if let Ok(offset) = from_value::<StartingOffset>(value.clone()) {
112+
if offset.starting_offset.is_some() {
113+
return Ok(ChosenParameters::Offset(offset));
114+
}
115+
}
116+
if let Ok(params) = from_value::<TpmParameters>(value) {
117+
Ok(ChosenParameters::Parameters(params))
118+
} else {
119+
Err("Failed to deserialize ChosenParameters".to_string())
120+
}
121+
}
122+
}
123+
124+
impl From<ChosenParameters> for JsonValue {
125+
fn from(params: ChosenParameters) -> Self {
126+
match params {
127+
ChosenParameters::Parameters(params) => to_value(params).unwrap(), //#[allow_ci]
128+
ChosenParameters::Offset(offset) => to_value(offset).unwrap(), //#[allow_ci]
129+
}
130+
}
131+
}
132+
133+
#[derive(Serialize, Deserialize, Debug, Clone)]
134+
pub struct AttestationKey {
135+
pub key_class: String,
136+
pub key_identifier: String,
137+
pub key_algorithm: String,
138+
pub public_hash: String,
139+
}
140+
141+
#[cfg(test)]
142+
mod tests {
143+
144+
use super::*;
145+
146+
#[test]
147+
fn serialize_request() {
148+
// Create a new AttestationRequest object and serialize it to JSON
149+
let request = AttestationRequest {
150+
data: RequestData {
151+
type_: "attestation".to_string(),
152+
attributes: Attributes {
153+
evidence_supported: vec![
154+
EvidenceSupported {
155+
evidence_class: "certification".to_string(),
156+
evidence_type: "tpm_quote".to_string(),
157+
agent_capabilities: Some(AgentCapabilities {
158+
spec_version: "2.0".to_string(),
159+
hash_algorithms: vec!["sha3_512".to_string()],
160+
signing_schemes: vec!["rsassa".to_string()],
161+
attestation_keys: vec![
162+
AttestationKeys {
163+
key_class: "private_key".to_string(),
164+
key_identifier: "att_key_identifier".to_string(),
165+
key_algorithm: "rsa".to_string(),
166+
public_hash: "cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411".to_string(),
167+
},
168+
],
169+
}),
170+
version: Some("2.1".to_string()),
171+
},
172+
],
173+
boot_time: "2024-11-12T16:21:17Z".to_string(),
174+
},
175+
},
176+
};
177+
let json = serde_json::to_string(&request).unwrap(); //#[allow_ci]
178+
println!("{}", json);
179+
assert_eq!(
180+
json,
181+
r#"{"data":{"type":"attestation","attributes":{"evidence_supported":[{"evidence_class":"certification","evidence_type":"tpm_quote","agent_capabilities":{"spec_version":"2.0","hash_algorithms":["sha3_512"],"signing_schemes":["rsassa"],"attestation_keys":[{"key_class":"private_key","key_identifier":"att_key_identifier","key_algorithm":"rsa","public_hash":"cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411"}]},"version":"2.1"}],"boot_time":"2024-11-12T16:21:17Z"}}}"#
182+
);
183+
}
184+
185+
#[test]
186+
fn deserialize_request() {
187+
// Create a JSON string and deserialize it to an AttestationRequest object
188+
let json = r#"
189+
{
190+
"data": {
191+
"type":"attestation",
192+
"attributes": {
193+
"evidence_supported":[{"evidence_class":"certification",
194+
"evidence_type":"tpm_quote",
195+
"agent_capabilities":{"spec_version":"2.0",
196+
"hash_algorithms":["sha3_512"],
197+
"signing_schemes":["rsassa"],
198+
"attestation_keys":[{"key_class":"private_key","key_identifier":"att_key_identifier",
199+
"key_algorithm":"rsa",
200+
"public_hash":"cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411"}]}},
201+
{"evidence_class":"full_log",
202+
"evidence_type":"mb_log",
203+
"version":"2.1"},
204+
{"evidence_class": "partial_log",
205+
"evidence_type": "ima_entries"}],
206+
"boot_time":"2024-11-12T16:21:17Z"
207+
}
208+
}
209+
}"#;
210+
let request: AttestationRequest = serde_json::from_str(json).unwrap(); //#[allow_ci]
211+
assert_eq!(request.data.type_, "attestation");
212+
assert_eq!(
213+
request.data.attributes.evidence_supported[0].evidence_class,
214+
"certification"
215+
);
216+
assert_eq!(
217+
request.data.attributes.evidence_supported[0].evidence_type,
218+
"tpm_quote"
219+
);
220+
let agent_capabilities = request.data.attributes.evidence_supported
221+
[0]
222+
.agent_capabilities
223+
.as_ref()
224+
.unwrap(); //#[allow_ci]
225+
assert_eq!(agent_capabilities.spec_version, "2.0");
226+
assert_eq!(agent_capabilities.hash_algorithms[0], "sha3_512");
227+
assert_eq!(agent_capabilities.signing_schemes[0], "rsassa");
228+
assert_eq!(
229+
agent_capabilities.attestation_keys[0].key_class,
230+
"private_key"
231+
);
232+
assert_eq!(
233+
agent_capabilities.attestation_keys[0].key_identifier,
234+
"att_key_identifier"
235+
);
236+
assert_eq!(
237+
agent_capabilities.attestation_keys[0].key_algorithm,
238+
"rsa"
239+
);
240+
assert_eq!(
241+
agent_capabilities
242+
.attestation_keys[0]
243+
.public_hash,
244+
"cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411"
245+
);
246+
assert_eq!(
247+
request.data.attributes.evidence_supported[1].evidence_class,
248+
"full_log"
249+
);
250+
assert_eq!(
251+
request.data.attributes.evidence_supported[1].evidence_type,
252+
"mb_log"
253+
);
254+
assert_eq!(
255+
request.data.attributes.evidence_supported[1].version,
256+
Some("2.1".to_string())
257+
);
258+
assert_eq!(
259+
request.data.attributes.evidence_supported[2].evidence_class,
260+
"partial_log"
261+
);
262+
assert_eq!(
263+
request.data.attributes.evidence_supported[2].evidence_type,
264+
"ima_entries"
265+
);
266+
assert_eq!(request.data.attributes.boot_time, "2024-11-12T16:21:17Z");
267+
}
268+
269+
#[test]
270+
fn serialize_response() {
271+
// Create a new AttestationResponse object and serialize it to JSON
272+
let response = AttestationResponse {
273+
data: ResponseData {
274+
type_: "attestation".to_string(),
275+
attributes: ResponseAttributes {
276+
evidence_requested: vec![
277+
EvidenceRequested {
278+
evidence_class: "certification".to_string(),
279+
evidence_type: "tpm_quote".to_string(),
280+
chosen_parameters: Some(ChosenParameters::Parameters(TpmParameters {
281+
nonce: Some("nonce".to_string()),
282+
pcr_selection: Some(vec![0]),
283+
hash_algorithm: Some("sha384".to_string()),
284+
signing_scheme: Some("rsassa".to_string()),
285+
attestation_key: Some(AttestationKey {
286+
key_class: "private_key".to_string(),
287+
key_identifier: "att_key_identifier".to_string(),
288+
key_algorithm: "rsa".to_string(),
289+
public_hash: "cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411".to_string(),
290+
}),
291+
})),
292+
version: None,
293+
},
294+
],
295+
boot_time: "2024-11-12T16:21:17Z".to_string(),
296+
},
297+
},
298+
};
299+
let json = serde_json::to_string(&response).unwrap(); //#[allow_ci]
300+
println!("{}", json);
301+
assert_eq!(
302+
json,
303+
r#"{"data":{"type":"attestation","attributes":{"evidence_requested":[{"evidence_class":"certification","evidence_type":"tpm_quote","chosen_parameters":{"attestation_key":{"key_algorithm":"rsa","key_class":"private_key","key_identifier":"att_key_identifier","public_hash":"cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411"},"hash_algorithm":"sha384","nonce":"nonce","pcr_selection":[0],"signing_scheme":"rsassa"}}],"boot_time":"2024-11-12T16:21:17Z"}}}"#
304+
);
305+
}
306+
307+
#[test]
308+
fn deserialize_response() {
309+
// Create a JSON string and deserialize it to an AttestationResponse object
310+
let json = r#"
311+
{
312+
"data": {
313+
"type":"attestation",
314+
"attributes": {
315+
"evidence_requested":[{"evidence_class":"certification",
316+
"evidence_type":"tpm_quote",
317+
"chosen_parameters":{"nonce":"nonce",
318+
"pcr_selection":[0],
319+
"hash_algorithm":"sha384",
320+
"signing_scheme":"rsassa",
321+
"attestation_key":{"key_class":"private_key",
322+
"key_identifier":"att_key_identifier",
323+
"key_algorithm":"rsa",
324+
"public_hash":"cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411"}}},
325+
{"evidence_class": "full_log",
326+
"evidence_type": "mb_log",
327+
"version": "2.1"},
328+
{"evidence_class": "partial_log",
329+
"evidence_type": "ima_entries",
330+
"chosen_parameters": {"starting_offset": 25}}],
331+
"boot_time":"2024-11-12T16:21:17Z"
332+
}
333+
}
334+
}"#;
335+
let response: AttestationResponse =
336+
serde_json::from_str(json).unwrap(); //#[allow_ci]
337+
assert_eq!(response.data.type_, "attestation");
338+
assert_eq!(
339+
response.data.attributes.evidence_requested[0].evidence_class,
340+
"certification"
341+
);
342+
assert_eq!(
343+
response.data.attributes.evidence_requested[0].evidence_type,
344+
"tpm_quote"
345+
);
346+
let some_chosen_parameters =
347+
response.data.attributes.evidence_requested[0]
348+
.chosen_parameters
349+
.as_ref();
350+
assert!(some_chosen_parameters.is_some());
351+
let chosen_parameters = some_chosen_parameters.unwrap(); //#[allow_ci]
352+
match chosen_parameters {
353+
ChosenParameters::Parameters(params) => {
354+
assert_eq!(params.nonce, Some("nonce".to_string()));
355+
assert_eq!(params.pcr_selection.as_ref().unwrap()[0], 0); //#[allow_ci]
356+
assert_eq!(params.hash_algorithm, Some("sha384".to_string()));
357+
assert_eq!(params.signing_scheme, Some("rsassa".to_string()));
358+
let attestation_key =
359+
params.attestation_key.as_ref().unwrap(); //#[allow_ci]
360+
assert_eq!(attestation_key.key_class, "private_key");
361+
assert_eq!(
362+
attestation_key.key_identifier,
363+
"att_key_identifier"
364+
);
365+
assert_eq!(attestation_key.key_algorithm, "rsa");
366+
assert_eq!(attestation_key
367+
.public_hash,
368+
"cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411"
369+
);
370+
}
371+
_ => panic!("Expected Parameters"), //#[allow_ci]
372+
}
373+
assert_eq!(
374+
response.data.attributes.evidence_requested[1].evidence_class,
375+
"full_log"
376+
);
377+
assert_eq!(
378+
response.data.attributes.evidence_requested[1].evidence_type,
379+
"mb_log"
380+
);
381+
assert_eq!(
382+
response.data.attributes.evidence_requested[1].version,
383+
Some("2.1".to_string())
384+
);
385+
assert_eq!(
386+
response.data.attributes.evidence_requested[2].evidence_class,
387+
"partial_log"
388+
);
389+
assert_eq!(
390+
response.data.attributes.evidence_requested[2].evidence_type,
391+
"ima_entries"
392+
);
393+
let some_chosen_parameters =
394+
response.data.attributes.evidence_requested[2]
395+
.chosen_parameters
396+
.as_ref();
397+
assert!(some_chosen_parameters.is_some());
398+
let chosen_parameters = some_chosen_parameters.unwrap(); //#[allow_ci]
399+
match chosen_parameters {
400+
ChosenParameters::Offset(offset) => {
401+
assert_eq!(offset.starting_offset, Some(25));
402+
}
403+
ChosenParameters::Parameters(params) => {
404+
println!("{:?}", params);
405+
panic!("Unexpected Parameters"); //#[allow_ci]
406+
}
407+
}
408+
assert_eq!(
409+
response.data.attributes.boot_time,
410+
"2024-11-12T16:21:17Z"
411+
);
412+
}
413+
}

‎keylime/src/structures/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod capabilities_negotiation;
2+
3+
pub use capabilities_negotiation::*;

0 commit comments

Comments
 (0)
Please sign in to comment.