Skip to content

Commit

Permalink
Add capabilities_negotiation structures
Browse files Browse the repository at this point in the history
This change aims to include those structures
that will be required to communicate
capabilities negotiation information for
Keylime Push model

Resolves: #933
Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com>
  • Loading branch information
sarroutbi committed Feb 25, 2025
1 parent 3b6c0ff commit 427c8f1
Show file tree
Hide file tree
Showing 3 changed files with 369 additions and 0 deletions.
1 change: 1 addition & 0 deletions keylime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod ip_parser;
pub mod list_parser;
pub mod registrar_client;
pub mod serialization;
pub mod structures;
pub mod tpm;
pub mod version;

Expand Down
365 changes: 365 additions & 0 deletions keylime/src/structures/capabilities_negotiation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
use serde::{Deserialize, Serialize};

// Define the structure for the AttestationRequest:
#[derive(Serialize, Deserialize, Debug)]
pub struct AttestationRequest {
#[serde(rename(serialize = "data", deserialize = "data"))]
pub data: RequestData,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct RequestData {
#[serde(rename(serialize = "type", deserialize = "type"))]
pub type_: String,
pub attributes: Attributes,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Attributes {
pub evidence_supported: Vec<EvidenceSupported>,
pub boot_time: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct EvidenceSupported {
pub evidence_class: String,
pub evidence_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_capabilities: Option<AgentCapabilities>,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct AgentCapabilities {
pub spec_version: String,
pub hash_algorithms: Vec<String>,
pub signing_schemes: Vec<String>,
pub attestation_keys: Vec<AttestationKeys>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct AttestationKeys {
pub key_class: String,
pub key_identifier: String,
pub key_algorithm: String,
pub public_hash: String,
}

// Define the structure for the AttestationResponse:
#[derive(Serialize, Deserialize, Debug)]
pub struct AttestationResponse {
#[serde(rename(serialize = "data", deserialize = "data"))]
pub data: ResponseData,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ResponseData {
#[serde(rename(serialize = "type", deserialize = "type"))]
pub type_: String,
pub attributes: ResponseAttributes,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ResponseAttributes {
pub evidence_requested: Vec<EvidenceRequested>,
pub boot_time: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct EvidenceRequested {
pub evidence_class: String,
pub evidence_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub chosen_parameters: Option<ChosenParameters>,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ChosenParameters {
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pcr_selection: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hash_algorithm: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signing_scheme: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attestation_key: Option<AttestationKey>,
#[serde(skip_serializing_if = "Option::is_none")]
pub starting_offset: Option<i32>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct AttestationKey {
pub key_class: String,
pub key_identifier: String,
pub key_algorithm: String,
pub public_hash: String,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn serialize_request() {
// Create a new AttestationRequest object and serialize it to JSON
let request = AttestationRequest {
data: RequestData {
type_: "attestation".to_string(),
attributes: Attributes {
evidence_supported: vec![
EvidenceSupported {
evidence_class: "certification".to_string(),
evidence_type: "tpm_quote".to_string(),
agent_capabilities: Some(AgentCapabilities {
spec_version: "2.0".to_string(),
hash_algorithms: vec!["sha3_512".to_string()],
signing_schemes: vec!["rsassa".to_string()],
attestation_keys: vec![
AttestationKeys {
key_class: "private_key".to_string(),
key_identifier: "att_key_identifier".to_string(),
key_algorithm: "rsa".to_string(),
public_hash: "cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411".to_string(),
},
],
}),
version: Some("2.1".to_string()),
},
],
boot_time: "2024-11-12T16:21:17Z".to_string(),
},
},
};
let json = serde_json::to_string(&request).unwrap(); //#[allow_ci]
println!("{}", json);
assert_eq!(
json,
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"}}}"#
);
}

#[test]
fn deserialize_request() {
// Create a JSON string and deserialize it to an AttestationRequest object
let json = 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"}]}},
{"evidence_class":"full_log",
"evidence_type":"mb_log",
"version":"2.1"},
{"evidence_class": "partial_log",
"evidence_type": "ima_entries"}],
"boot_time":"2024-11-12T16:21:17Z"
}
}
}"#;
let request: AttestationRequest = serde_json::from_str(json).unwrap(); //#[allow_ci]
assert_eq!(request.data.type_, "attestation");
assert_eq!(
request.data.attributes.evidence_supported[0].evidence_class,
"certification"
);
assert_eq!(
request.data.attributes.evidence_supported[0].evidence_type,
"tpm_quote"
);
let agent_capabilities = request.data.attributes.evidence_supported
[0]
.agent_capabilities
.as_ref()
.unwrap(); //#[allow_ci]
assert_eq!(agent_capabilities.spec_version, "2.0");
assert_eq!(agent_capabilities.hash_algorithms[0], "sha3_512");
assert_eq!(agent_capabilities.signing_schemes[0], "rsassa");
assert_eq!(
agent_capabilities.attestation_keys[0].key_class,
"private_key"
);
assert_eq!(
agent_capabilities.attestation_keys[0].key_identifier,
"att_key_identifier"
);
assert_eq!(
agent_capabilities.attestation_keys[0].key_algorithm,
"rsa"
);
assert_eq!(
agent_capabilities
.attestation_keys[0]
.public_hash,
"cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411"
);
assert_eq!(
request.data.attributes.evidence_supported[1].evidence_class,
"full_log"
);
assert_eq!(
request.data.attributes.evidence_supported[1].evidence_type,
"mb_log"
);
assert_eq!(
request.data.attributes.evidence_supported[1].version,
Some("2.1".to_string())
);
assert_eq!(
request.data.attributes.evidence_supported[2].evidence_class,
"partial_log"
);
assert_eq!(
request.data.attributes.evidence_supported[2].evidence_type,
"ima_entries"
);
assert_eq!(request.data.attributes.boot_time, "2024-11-12T16:21:17Z");
}

#[test]
fn serialize_response() {
// Create a new AttestationResponse object and serialize it to JSON
let response = AttestationResponse {
data: ResponseData {
type_: "attestation".to_string(),
attributes: ResponseAttributes {
evidence_requested: vec![
EvidenceRequested {
evidence_class: "certification".to_string(),
evidence_type: "tpm_quote".to_string(),
chosen_parameters: Some(ChosenParameters {
nonce: Some("nonce".to_string()),
pcr_selection: Some(vec![0]),
hash_algorithm: Some("sha384".to_string()),
signing_scheme: Some("rsassa".to_string()),
attestation_key: Some(AttestationKey {
key_class: "private_key".to_string(),
key_identifier: "att_key_identifier".to_string(),
key_algorithm: "rsa".to_string(),
public_hash: "cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411".to_string(),
}),
starting_offset: None,
}),
version: None,
},
],
boot_time: "2024-11-12T16:21:17Z".to_string(),
},
},
};
let json = serde_json::to_string(&response).unwrap(); //#[allow_ci]
println!("{}", json);
assert_eq!(
json,
r#"{"data":{"type":"attestation","attributes":{"evidence_requested":[{"evidence_class":"certification","evidence_type":"tpm_quote","chosen_parameters":{"nonce":"nonce","pcr_selection":[0],"hash_algorithm":"sha384","signing_scheme":"rsassa","attestation_key":{"key_class":"private_key","key_identifier":"att_key_identifier","key_algorithm":"rsa","public_hash":"cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411"}}}],"boot_time":"2024-11-12T16:21:17Z"}}}"#
);
}

#[test]
fn deserialize_response() {
// Create a JSON string and deserialize it to an AttestationResponse object
let json = r#"
{
"data": {
"type":"attestation",
"attributes": {
"evidence_requested":[{"evidence_class":"certification",
"evidence_type":"tpm_quote",
"chosen_parameters":{"nonce":"nonce",
"pcr_selection":[0],
"hash_algorithm":"sha384",
"signing_scheme":"rsassa",
"attestation_key":{"key_class":"private_key",
"key_identifier":"att_key_identifier",
"key_algorithm":"rsa",
"public_hash":"cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411"}}},
{"evidence_class": "full_log",
"evidence_type": "mb_log",
"version": "2.1"},
{"evidence_class": "partial_log",
"evidence_type": "ima_entries",
"chosen_parameters": {"starting_offset": 25}}],
"boot_time":"2024-11-12T16:21:17Z"
}
}
}"#;
let response: AttestationResponse =
serde_json::from_str(json).unwrap(); //#[allow_ci]
assert_eq!(response.data.type_, "attestation");
assert_eq!(
response.data.attributes.evidence_requested[0].evidence_class,
"certification"
);
assert_eq!(
response.data.attributes.evidence_requested[0].evidence_type,
"tpm_quote"
);
let some_chosen_parameters =
response.data.attributes.evidence_requested[0]
.chosen_parameters
.as_ref();
assert_eq!(some_chosen_parameters.is_some(), true);
let chosen_parameters = some_chosen_parameters.unwrap(); //#[allow_ci]
assert_eq!(chosen_parameters.nonce, Some("nonce".to_string()));
// check pcr_selection
assert_eq!(chosen_parameters.pcr_selection.as_ref().unwrap()[0], 0); //#[allow_ci]
assert_eq!(
chosen_parameters.hash_algorithm,
Some("sha384".to_string())
);
assert_eq!(
chosen_parameters.signing_scheme,
Some("rsassa".to_string())
);
let attestation_key =
chosen_parameters.attestation_key.as_ref().unwrap(); //#[allow_ci]
assert_eq!(attestation_key.key_class, "private_key");
assert_eq!(attestation_key.key_identifier, "att_key_identifier");
assert_eq!(attestation_key.key_algorithm, "rsa");
assert_eq!(attestation_key
.public_hash,
"cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411"
);
assert_eq!(
response.data.attributes.evidence_requested[1].evidence_class,
"full_log"
);
assert_eq!(
response.data.attributes.evidence_requested[1].evidence_type,
"mb_log"
);
assert_eq!(
response.data.attributes.evidence_requested[1].version,
Some("2.1".to_string())
);
assert_eq!(
response.data.attributes.evidence_requested[2].evidence_class,
"partial_log"
);
assert_eq!(
response.data.attributes.evidence_requested[2].evidence_type,
"ima_entries"
);
let some_chosen_parameters =
response.data.attributes.evidence_requested[2]
.chosen_parameters
.as_ref();
assert_eq!(some_chosen_parameters.is_some(), true);
let chosen_parameters = some_chosen_parameters.unwrap(); //#[allow_ci]
assert_eq!(chosen_parameters.starting_offset, Some(25));
assert_eq!(
response.data.attributes.boot_time,
"2024-11-12T16:21:17Z"
);
}
}
3 changes: 3 additions & 0 deletions keylime/src/structures/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod capabilities_negotiation;

pub use capabilities_negotiation::*;

0 comments on commit 427c8f1

Please sign in to comment.