diff --git a/keylime-agent/src/agent_handler.rs b/keylime-agent/src/agent_handler.rs new file mode 100644 index 000000000..11d021864 --- /dev/null +++ b/keylime-agent/src/agent_handler.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Keylime Authors + +use crate::common::JsonWrapper; +use crate::{tpm, Error as KeylimeError, QuoteData}; +use actix_web::{web, HttpRequest, HttpResponse, Responder}; +use base64::{engine::general_purpose, Engine as _}; +use log::*; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub(crate) struct AgentInfo { + pub agent_uuid: String, + pub tpm_hash_alg: String, + pub tpm_enc_alg: String, + pub tpm_sign_alg: String, + pub ak_handle: u32, +} + +// This is an Info request which gets some information about this keylime agent +// It should return a AgentInfo object as JSON +pub async fn info( + req: HttpRequest, + data: web::Data, +) -> impl Responder { + debug!("Returning agent information"); + + let mut info = AgentInfo { + agent_uuid: data.agent_uuid.clone(), + tpm_hash_alg: data.hash_alg.to_string(), + tpm_enc_alg: data.enc_alg.to_string(), + tpm_sign_alg: data.sign_alg.to_string(), + ak_handle: data.ak_handle.value(), + }; + + let response = JsonWrapper::success(info); + info!("GET info returning 200 response"); + HttpResponse::Ok().json(response) +} + +#[cfg(test)] +#[cfg(feature = "testing")] +mod tests { + use super::*; + use crate::common::API_VERSION; + use actix_web::{test, web, App}; + + #[actix_rt::test] + async fn test_agent_info() { + let mut quotedata = QuoteData::fixture().unwrap(); //#[allow_ci] + quotedata.hash_alg = keylime::algorithms::HashAlgorithm::Sha256; + quotedata.enc_alg = keylime::algorithms::EncryptionAlgorithm::Rsa; + quotedata.sign_alg = keylime::algorithms::SignAlgorithm::RsaSsa; + quotedata.agent_uuid = "DEADBEEF".to_string(); + let data = web::Data::new(quotedata); + let mut app = + test::init_service(App::new().app_data(data.clone()).route( + &format!("/{API_VERSION}/agent/info"), + web::get().to(info), + )) + .await; + + let req = test::TestRequest::get() + .uri(&format!("/{API_VERSION}/agent/info")) + .to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_success()); + + let result: JsonWrapper = test::read_body_json(resp).await; + assert_eq!(result.results.agent_uuid.as_str(), "DEADBEEF"); + assert_eq!(result.results.tpm_hash_alg.as_str(), "sha256"); + assert_eq!(result.results.tpm_enc_alg.as_str(), "rsa"); + assert_eq!(result.results.tpm_sign_alg.as_str(), "rsassa"); + } +} diff --git a/keylime-agent/src/common.rs b/keylime-agent/src/common.rs index e3bbfa33a..a6cb0702e 100644 --- a/keylime-agent/src/common.rs +++ b/keylime-agent/src/common.rs @@ -36,7 +36,7 @@ use tss_esapi::{ /* * Constants and static variables */ -pub const API_VERSION: &str = "v2.1"; +pub const API_VERSION: &str = "v2.2"; pub const TPM_DATA_PCR: usize = 16; pub const IMA_PCR: usize = 10; pub static RSA_PUBLICKEY_EXPORTABLE: &str = "rsa placeholder"; diff --git a/keylime-agent/src/errors_handler.rs b/keylime-agent/src/errors_handler.rs index b0fa4c243..7d9c97755 100644 --- a/keylime-agent/src/errors_handler.rs +++ b/keylime-agent/src/errors_handler.rs @@ -20,7 +20,7 @@ pub(crate) async fn app_default(req: HttpRequest) -> impl Responder { http::Method::GET => { error = 400; message = format!( - "Not Implemented: Use /version or /{API_VERSION}/ interfaces" + "Not Implemented: Use /version or /{API_VERSION} interfaces" ); response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, &message)); @@ -28,7 +28,7 @@ pub(crate) async fn app_default(req: HttpRequest) -> impl Responder { http::Method::POST => { error = 400; message = - format!("Not Implemented: Use /{API_VERSION}/ interface"); + format!("Not Implemented: Use /{API_VERSION} interface"); response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, &message)); } @@ -62,14 +62,15 @@ pub(crate) async fn api_default(req: HttpRequest) -> impl Responder { match req.head().method { http::Method::GET => { error = 400; - message = "Not Implemented: Use /keys/ or /quotes/ interfaces"; + message = + "Not Implemented: Use /agent, /keys, or /quotes interfaces"; response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, message)); } http::Method::POST => { error = 400; message = - "Not Implemented: Use /keys/ or /notifications/ interfaces"; + "Not Implemented: Use /keys or /notifications interfaces"; response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, message)); } @@ -103,19 +104,19 @@ pub(crate) async fn keys_default(req: HttpRequest) -> impl Responder { match req.head().method { http::Method::GET => { error = 400; - message = "URI not supported, only /pubkey and /verify are supported for GET in /keys/ interface"; + message = "URI not supported, only /pubkey and /verify are supported for GET in /keys interface"; response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, message)); } http::Method::POST => { error = 400; - message = "URI not supported, only /ukey and /vkey are supported for POST in /keys/ interface"; + message = "URI not supported, only /ukey and /vkey are supported for POST in /keys interface"; response = HttpResponse::BadRequest() .json(JsonWrapper::error(error, message)); } _ => { error = 405; - message = "Method is not supported in /keys/ interface"; + message = "Method is not supported in /keys interface"; response = HttpResponse::MethodNotAllowed() .insert_header(http::header::Allow(vec![ http::Method::GET, @@ -166,6 +167,37 @@ pub(crate) async fn quotes_default(req: HttpRequest) -> impl Responder { response } +pub(crate) async fn agent_default(req: HttpRequest) -> impl Responder { + let error; + let response; + let message; + + match req.head().method { + http::Method::GET => { + error = 400; + message = "URI not supported, only /info is supported for GET in /agent interface"; + response = HttpResponse::BadRequest() + .json(JsonWrapper::error(error, message)); + } + _ => { + error = 405; + message = "Method is not supported in /agent interface"; + response = HttpResponse::MethodNotAllowed() + .insert_header(http::header::Allow(vec![http::Method::GET])) + .json(JsonWrapper::error(error, message)); + } + }; + + warn!( + "{} returning {} response. {}", + req.head().method, + error, + message + ); + + response +} + pub(crate) async fn notifications_default( req: HttpRequest, ) -> impl Responder { @@ -343,6 +375,11 @@ mod tests { .await } + #[actix_rt::test] + async fn test_agent_default() { + test_default(web::resource("/").to(agent_default), "GET").await + } + #[derive(Serialize, Deserialize)] struct DummyQuery { param: String, @@ -395,10 +432,10 @@ mod tests { .error_handler(path_parser_error), ) .service( - web::resource("/v2.1/ok").route(web::get().to(dummy)), + web::resource("/v2.2/ok").route(web::get().to(dummy)), ) .service( - web::resource("/v2.1/ok/{number}/{string}") + web::resource("/v2.2/ok/{number}/{string}") .route(web::get().to(dummy_with_path)), ) .service( @@ -410,7 +447,7 @@ mod tests { // Sanity well formed request let req = test::TestRequest::get() - .uri("/v2.1/ok?param=Test") + .uri("/v2.2/ok?param=Test") .set_json(&DummyPayload { field: 42 }) .to_request(); @@ -432,7 +469,7 @@ mod tests { // Test JSON parsing error let req = test::TestRequest::get() - .uri("/v2.1/ok?param=Test") + .uri("/v2.2/ok?param=Test") .insert_header(http::header::ContentType::json()) .set_payload("Not JSON") .to_request(); @@ -445,7 +482,7 @@ mod tests { // Test Query parsing error let req = test::TestRequest::get() - .uri("/v2.1/ok?test=query") + .uri("/v2.2/ok?test=query") .set_json(&DummyPayload { field: 42 }) .to_request(); let resp = test::call_service(&app, req).await; @@ -457,7 +494,7 @@ mod tests { // Test Path parsing error let req = test::TestRequest::get() - .uri("/v2.1/ok/something/42?test=query") + .uri("/v2.2/ok/something/42?test=query") .set_json(&DummyPayload { field: 42 }) .to_request(); let resp = test::call_service(&app, req).await; diff --git a/keylime-agent/src/main.rs b/keylime-agent/src/main.rs index f0713f9b4..b418ac46c 100644 --- a/keylime-agent/src/main.rs +++ b/keylime-agent/src/main.rs @@ -31,6 +31,7 @@ // missing_docs: there is many functions missing documentations for now #![allow(unused, missing_docs)] +mod agent_handler; mod common; mod config; mod error; @@ -861,6 +862,15 @@ async fn main() -> Result<()> { ) .service( web::scope(&format!("/{API_VERSION}")) + .service( + web::scope("/agent") + .service(web::resource("/info").route( + web::get().to(agent_handler::info), + )) + .default_service(web::to( + errors_handler::agent_default, + )), + ) .service( web::scope("/keys") .service(web::resource("/pubkey").route(