From c89b96b32e9f278e87018e357fbf21bea0087417 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Fri, 27 Sep 2024 14:41:51 +0800 Subject: [PATCH] KBS: refactor attestation module This refactoring combines all RCAR (attestation) related code into one module. This would help to better modularization and error handling. Signed-off-by: Xynnn007 --- kbs/src/attestation/backend.rs | 362 ++++++++++++++++++ kbs/src/attestation/coco/builtin.rs | 3 +- kbs/src/attestation/coco/grpc.rs | 9 +- kbs/src/attestation/config.rs | 29 ++ kbs/src/attestation/error.rs | 42 ++ .../attestation/intel_trust_authority/mod.rs | 7 +- kbs/src/attestation/mod.rs | 191 +-------- kbs/src/{ => attestation}/session.rs | 4 +- kbs/src/http/attest.rs | 153 -------- 9 files changed, 449 insertions(+), 351 deletions(-) create mode 100644 kbs/src/attestation/backend.rs create mode 100644 kbs/src/attestation/config.rs create mode 100644 kbs/src/attestation/error.rs rename kbs/src/{ => attestation}/session.rs (95%) delete mode 100644 kbs/src/http/attest.rs diff --git a/kbs/src/attestation/backend.rs b/kbs/src/attestation/backend.rs new file mode 100644 index 000000000..febc9e490 --- /dev/null +++ b/kbs/src/attestation/backend.rs @@ -0,0 +1,362 @@ +// Copyright (c) 2024 by Alibaba. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::Arc; + +use actix_web::{HttpRequest, HttpResponse}; +use anyhow::{anyhow, bail, Context}; +use async_trait::async_trait; +use base64::{engine::general_purpose::STANDARD, Engine}; +use kbs_types::{Attestation, Challenge, Request, Tee}; +use lazy_static::lazy_static; +use log::{debug, info}; +use rand::{thread_rng, Rng}; +use semver::{BuildMetadata, Prerelease, Version, VersionReq}; +use serde::Deserialize; +use serde_json::json; + +use crate::attestation::session::KBS_SESSION_ID; + +use super::{ + config::{AttestationConfig, AttestationServiceConfig}, + session::{SessionMap, SessionStatus}, + Error, Result, +}; + +static KBS_MAJOR_VERSION: u64 = 0; +static KBS_MINOR_VERSION: u64 = 1; +static KBS_PATCH_VERSION: u64 = 1; + +lazy_static! { + static ref VERSION_REQ: VersionReq = { + let kbs_version = Version { + major: KBS_MAJOR_VERSION, + minor: KBS_MINOR_VERSION, + patch: KBS_PATCH_VERSION, + pre: Prerelease::EMPTY, + build: BuildMetadata::EMPTY, + }; + + VersionReq::parse(&format!("={kbs_version}")).unwrap() + }; +} + +/// Number of bytes in a nonce. +const NONCE_SIZE_BYTES: usize = 32; + +/// Create a nonce and return as a base-64 encoded string. +pub async fn make_nonce() -> anyhow::Result { + let mut nonce: Vec = vec![0; NONCE_SIZE_BYTES]; + + thread_rng() + .try_fill(&mut nonce[..]) + .map_err(anyhow::Error::from)?; + + Ok(STANDARD.encode(&nonce)) +} + +pub(crate) async fn generic_generate_challenge( + _tee: Tee, + _tee_parameters: serde_json::Value, +) -> anyhow::Result { + let nonce = make_nonce().await?; + + Ok(Challenge { + nonce, + extra_params: serde_json::Value::String(String::new()), + }) +} + +/// Interface for Attestation Services. +/// +/// Attestation Service implementations should implement this interface. +#[async_trait] +pub trait Attest: Send + Sync { + /// Set Attestation Policy + async fn set_policy(&self, _policy_id: &str, _policy: &str) -> anyhow::Result<()> { + Err(anyhow!("Set Policy API is unimplemented")) + } + + /// Verify Attestation Evidence + /// Return Attestation Results Token + async fn verify(&self, tee: Tee, nonce: &str, attestation: &str) -> anyhow::Result; + + /// generate the Challenge to pass to attester based on Tee and nonce + async fn generate_challenge( + &self, + tee: Tee, + tee_parameters: serde_json::Value, + ) -> anyhow::Result { + generic_generate_challenge(tee, tee_parameters).await + } +} + +/// Attestation Service +#[derive(Clone)] +pub struct AttestationService { + /// Attestation Module + inner: Arc, + + /// A concurrent safe map to keep status of RCAR status + session_map: Arc, + + /// Maximum session expiration time. + timeout: i64, +} + +#[derive(Deserialize, Debug)] +pub struct SetPolicyInput { + policy_id: String, + policy: String, +} + +impl AttestationService { + pub async fn new(config: AttestationConfig) -> Result { + let inner = match config.attestation_service { + #[cfg(any(feature = "coco-as-builtin", feature = "coco-as-builtin-no-verifier"))] + AttestationServiceConfig::CoCoASBuiltIn(cfg) => { + let built_in_as = super::coco::builtin::BuiltInCoCoAs::new(cfg) + .await + .map_err(|e| Error::AttestationServiceInitialization { source: e })?; + Arc::new(built_in_as) as _ + } + #[cfg(feature = "coco-as-grpc")] + AttestationServiceConfig::CoCoASGrpc(cfg) => { + let grpc_coco_as = super::coco::grpc::GrpcClientPool::new(cfg) + .await + .map_err(|e| Error::AttestationServiceInitialization { source: e })?; + Arc::new(grpc_coco_as) as _ + } + #[cfg(feature = "intel-trust-authority-as")] + AttestationServiceConfig::IntelTA(cfg) => { + let intel_ta = super::intel_trust_authority::IntelTrustAuthority::new(cfg) + .await + .map_err(|e| Error::AttestationServiceInitialization { source: e })?; + Arc::new(intel_ta) as _ + } + }; + + let session_map = Arc::new(SessionMap::new()); + + tokio::spawn({ + let session_map_clone = session_map.clone(); + async move { + loop { + tokio::time::sleep(std::time::Duration::from_secs(60)).await; + session_map_clone + .sessions + .retain_async(|_, v| !v.is_expired()) + .await; + } + } + }); + Ok(Self { + inner, + timeout: config.timeout, + session_map, + }) + } + + pub async fn set_policy(&self, request: &[u8]) -> Result<()> { + self.__set_policy(request) + .await + .map_err(|e| Error::SetPolicy { source: e }) + } + + async fn __set_policy(&self, request: &[u8]) -> anyhow::Result<()> { + let input: SetPolicyInput = + serde_json::from_slice(request).context("parse set policy request")?; + self.inner.set_policy(&input.policy_id, &input.policy).await + } + + pub async fn auth(&self, request: &[u8]) -> Result { + self.__auth(request) + .await + .map_err(|e| Error::RcarAuthFailed { source: e }) + } + + async fn __auth(&self, request: &[u8]) -> anyhow::Result { + let request: Request = serde_json::from_slice(request).context("deserialize Request")?; + let version = Version::parse(&request.version).context("failed to parse KBS version")?; + if !VERSION_REQ.matches(&version) { + bail!( + "expected version: {}, requested version: {}", + *VERSION_REQ, + request.version + ); + } + + let challenge = self + .inner + .generate_challenge(request.tee, request.extra_params.clone()) + .await + .context("generate challenge")?; + + let session = SessionStatus::auth(request, self.timeout, challenge).context("Session")?; + + let response = HttpResponse::Ok() + .cookie(session.cookie()) + .json(session.challenge()); + + self.session_map.insert(session); + + Ok(response) + } + + pub async fn attest(&self, attestation: &[u8], request: HttpRequest) -> Result { + self.__attest(attestation, request) + .await + .map_err(|e| Error::RcarAttestFailed { source: e }) + } + + async fn __attest( + &self, + attestation: &[u8], + request: HttpRequest, + ) -> anyhow::Result { + let cookie = request.cookie(KBS_SESSION_ID).context("cookie not found")?; + + let session_id = cookie.value(); + + let attestation: Attestation = + serde_json::from_slice(attestation).context("deserialize Attestation")?; + let (tee, nonce) = { + let session = self + .session_map + .sessions + .get_async(session_id) + .await + .ok_or(anyhow!("No cookie found"))?; + let session = session.get(); + + debug!("Session ID {}", session.id()); + + if session.is_expired() { + bail!("session expired."); + } + + if let SessionStatus::Attested { token, .. } = session { + debug!( + "Session {} is already attested. Skip attestation and return the old token", + session.id() + ); + let body = serde_json::to_string(&json!({ + "token": token, + })) + .context("Serialize token failed")?; + + return Ok(HttpResponse::Ok() + .cookie(session.cookie()) + .content_type("application/json") + .body(body)); + } + + let attestation_str = serde_json::to_string_pretty(&attestation) + .context("Failed to serialize Attestation")?; + debug!("Attestation: {attestation_str}"); + + (session.request().tee, session.challenge().nonce.to_string()) + }; + + let attestation_str = + serde_json::to_string(&attestation).context("serialize attestation failed")?; + let token = self + .inner + .verify(tee, &nonce, &attestation_str) + .await + .context("verify TEE evidence failed")?; + + let mut session = self + .session_map + .sessions + .get_async(session_id) + .await + .ok_or(anyhow!("session not found"))?; + let session = session.get_mut(); + + let body = serde_json::to_string(&json!({ + "token": token, + })) + .context("Serialize token failed")?; + + session.attest(token); + + Ok(HttpResponse::Ok() + .cookie(session.cookie()) + .content_type("application/json") + .body(body)) + } + + pub async fn get_attest_token_from_session( + &self, + request: &HttpRequest, + ) -> anyhow::Result { + let cookie = request + .cookie(KBS_SESSION_ID) + .context("KBS session cookie not found")?; + + let session = self + .session_map + .sessions + .get_async(cookie.value()) + .await + .context("session not found")?; + + let session = session.get(); + + info!("Cookie {} request to get resource", session.id()); + + if session.is_expired() { + bail!("The session is expired"); + } + + let SessionStatus::Attested { token, .. } = session else { + bail!("The session is not authorized"); + }; + + Ok(token.to_owned()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_make_nonce() { + const BITS_PER_BYTE: usize = 8; + + /// A base-64 encoded value is this many bits in length. + const BASE64_BITS_CHUNK: usize = 6; + + /// Number of bytes that base64 encoding requires the result to align on. + const BASE64_ROUNDING_MULTIPLE: usize = 4; + + /// The nominal base64 encoded length. + const BASE64_NONCE_LENGTH_UNROUNDED_BYTES: usize = + (NONCE_SIZE_BYTES * BITS_PER_BYTE) / BASE64_BITS_CHUNK; + + /// The actual base64 encoded length is rounded up to the specified multiple. + const EXPECTED_LENGTH_BYTES: usize = + BASE64_NONCE_LENGTH_UNROUNDED_BYTES.next_multiple_of(BASE64_ROUNDING_MULTIPLE); + + // Number of nonce tests to run (arbitrary) + let nonce_count = 13; + + let mut nonces = vec![]; + + for _ in 0..nonce_count { + let nonce = make_nonce().await.unwrap(); + + assert_eq!(nonce.len(), EXPECTED_LENGTH_BYTES); + + let found = nonces.contains(&nonce); + + // The nonces should be unique + assert_eq!(found, false); + + nonces.push(nonce); + } + } +} diff --git a/kbs/src/attestation/coco/builtin.rs b/kbs/src/attestation/coco/builtin.rs index cc0bdcf9d..a6b9faaf0 100644 --- a/kbs/src/attestation/coco/builtin.rs +++ b/kbs/src/attestation/coco/builtin.rs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -use crate::attestation::{make_nonce, Attest}; use anyhow::*; use async_trait::async_trait; use attestation_service::{config::Config as AsConfig, AttestationService, Data, HashAlgorithm}; @@ -10,6 +9,8 @@ use kbs_types::{Attestation, Challenge, Tee}; use serde_json::json; use tokio::sync::RwLock; +use crate::attestation::backend::{make_nonce, Attest}; + pub struct BuiltInCoCoAs { inner: RwLock, } diff --git a/kbs/src/attestation/coco/grpc.rs b/kbs/src/attestation/coco/grpc.rs index 903dbf344..7e4d9a3da 100644 --- a/kbs/src/attestation/coco/grpc.rs +++ b/kbs/src/attestation/coco/grpc.rs @@ -2,23 +2,20 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -use crate::attestation::{make_nonce, Attest}; use anyhow::*; use async_trait::async_trait; -use base64::{ - engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}, - Engine, -}; +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; use kbs_types::{Attestation, Challenge, Tee}; use log::info; use mobc::{Manager, Pool}; -use rand::{thread_rng, Rng}; use serde::Deserialize; use serde_json::json; use std::collections::HashMap; use tokio::sync::Mutex; use tonic::transport::Channel; +use crate::attestation::backend::{make_nonce, Attest}; + use self::attestation::{ attestation_request::RuntimeData, attestation_service_client::AttestationServiceClient, AttestationRequest, ChallengeRequest, SetPolicyRequest, diff --git a/kbs/src/attestation/config.rs b/kbs/src/attestation/config.rs new file mode 100644 index 000000000..f49be5a3b --- /dev/null +++ b/kbs/src/attestation/config.rs @@ -0,0 +1,29 @@ +// Copyright (c) 2024 by Alibaba. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use serde::Deserialize; + +#[derive(Clone, Debug, Deserialize, PartialEq)] +pub struct AttestationConfig { + #[serde(flatten)] + pub attestation_service: AttestationServiceConfig, + + pub timeout: i64, +} + +#[derive(Clone, Debug, Deserialize, PartialEq)] +#[serde(tag = "type")] +pub enum AttestationServiceConfig { + #[cfg(any(feature = "coco-as-builtin", feature = "coco-as-builtin-no-verifier"))] + #[serde(alias = "coco_as_builtin")] + CoCoASBuiltIn(attestation_service::config::Config), + + #[cfg(feature = "coco-as-grpc")] + #[serde(alias = "coco_as_grpc")] + CoCoASGrpc(super::coco::grpc::GrpcConfig), + + #[cfg(feature = "intel-trust-authority-as")] + #[serde(alias = "intel_ta")] + IntelTA(super::intel_trust_authority::IntelTrustAuthorityConfig), +} diff --git a/kbs/src/attestation/error.rs b/kbs/src/attestation/error.rs new file mode 100644 index 000000000..c00bf1396 --- /dev/null +++ b/kbs/src/attestation/error.rs @@ -0,0 +1,42 @@ +// Copyright (c) 2024 by Alibaba. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +use log::error; +use strum::AsRefStr; +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Error, AsRefStr, Debug)] +pub enum Error { + #[error("Failed to initialize attestation service")] + AttestationServiceInitialization { + #[source] + source: anyhow::Error, + }, + + #[error("Failed to extract Tee public key from claims")] + ExtractTeePubKeyFailed { + #[source] + source: anyhow::Error, + }, + + #[error("RCAR handshake Auth failed")] + RcarAuthFailed { + #[source] + source: anyhow::Error, + }, + + #[error("RCAR handshake Attest failed")] + RcarAttestFailed { + #[source] + source: anyhow::Error, + }, + + #[error("Set Attestation Policy failed")] + SetPolicy { + #[source] + source: anyhow::Error, + }, +} diff --git a/kbs/src/attestation/intel_trust_authority/mod.rs b/kbs/src/attestation/intel_trust_authority/mod.rs index 7986fd4ec..bbfe6f889 100644 --- a/kbs/src/attestation/intel_trust_authority/mod.rs +++ b/kbs/src/attestation/intel_trust_authority/mod.rs @@ -2,9 +2,10 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -use super::Attest; -use crate::attestation::{generic_generate_challenge, make_nonce}; -use crate::token::{jwk::JwkAttestationTokenVerifier, AttestationTokenVerifierConfig}; +use crate::{ + attestation::backend::{generic_generate_challenge, make_nonce, Attest}, + token::{jwk::JwkAttestationTokenVerifier, AttestationTokenVerifierConfig}, +}; use anyhow::*; use async_trait::async_trait; use az_cvm_vtpm::hcl::HclReport; diff --git a/kbs/src/attestation/mod.rs b/kbs/src/attestation/mod.rs index 6141304f8..10d9ca88d 100644 --- a/kbs/src/attestation/mod.rs +++ b/kbs/src/attestation/mod.rs @@ -2,196 +2,17 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -use anyhow::*; -use async_trait::async_trait; -#[cfg(any(feature = "coco-as-builtin", feature = "coco-as-builtin-no-verifier"))] -use attestation_service::config::Config as AsConfig; -use base64::{engine::general_purpose::STANDARD, Engine}; -#[cfg(feature = "coco-as-grpc")] -use coco::grpc::*; -#[cfg(feature = "intel-trust-authority-as")] -use intel_trust_authority::*; -use kbs_types::{Challenge, Tee}; -use rand::{thread_rng, Rng}; - -#[cfg(not(feature = "intel-trust-authority-as"))] -pub const AS_TOKEN_TEE_PUBKEY_PATH: &str = "/customized_claims/runtime_data/tee-pubkey"; -#[cfg(feature = "intel-trust-authority-as")] -pub const AS_TOKEN_TEE_PUBKEY_PATH: &str = "/attester_runtime_data/tee-pubkey"; - #[cfg(feature = "coco-as")] -#[allow(missing_docs)] pub mod coco; #[cfg(feature = "intel-trust-authority-as")] pub mod intel_trust_authority; -/// Number of bytes in a nonce. -const NONCE_SIZE_BYTES: usize = 32; - -/// Create a nonce and return as a base-64 encoded string. -pub async fn make_nonce() -> Result { - let mut nonce: Vec = vec![0; NONCE_SIZE_BYTES]; - - thread_rng() - .try_fill(&mut nonce[..]) - .map_err(anyhow::Error::from)?; - - Ok(STANDARD.encode(&nonce)) -} - -pub(crate) async fn generic_generate_challenge( - _tee: Tee, - _tee_parameters: serde_json::Value, -) -> Result { - let nonce = make_nonce().await?; - - Ok(Challenge { - nonce, - extra_params: serde_json::Value::String(String::new()), - }) -} - -/// Interface for Attestation Services. -/// -/// Attestation Service implementations should implement this interface. -#[async_trait] -pub trait Attest: Send + Sync { - /// Set Attestation Policy - async fn set_policy(&self, _policy_id: &str, _policy: &str) -> Result<()> { - Err(anyhow!("Set Policy API is unimplemented")) - } - - /// Verify Attestation Evidence - /// Return Attestation Results Token - async fn verify(&self, tee: Tee, nonce: &str, attestation: &str) -> Result; - - /// generate the Challenge to pass to attester based on Tee and nonce - async fn generate_challenge( - &self, - tee: Tee, - tee_parameters: serde_json::Value, - ) -> Result { - generic_generate_challenge(tee, tee_parameters).await - } -} - -/// Attestation Service -pub enum AttestationService { - #[cfg(any(feature = "coco-as-builtin", feature = "coco-as-builtin-no-verifier"))] - CoCoASBuiltIn(coco::builtin::BuiltInCoCoAs), - - #[cfg(feature = "coco-as-grpc")] - CoCoASgRPC(GrpcClientPool), - - #[cfg(feature = "intel-trust-authority-as")] - IntelTA(IntelTrustAuthority), -} - -impl AttestationService { - /// Create and initialize AttestationService. - #[cfg(any(feature = "coco-as-builtin", feature = "coco-as-builtin-no-verifier"))] - pub async fn new(config: AsConfig) -> Result { - let built_in_as = coco::builtin::BuiltInCoCoAs::new(config).await?; - Ok(Self::CoCoASBuiltIn(built_in_as)) - } - - /// Create and initialize AttestationService. - #[cfg(feature = "coco-as-grpc")] - pub async fn new(config: GrpcConfig) -> Result { - let pool = GrpcClientPool::new(config).await?; - Ok(Self::CoCoASgRPC(pool)) - } - - /// Create and initialize AttestationService. - #[cfg(feature = "intel-trust-authority-as")] - pub async fn new(config: IntelTrustAuthorityConfig) -> Result { - let ta_client = intel_trust_authority::IntelTrustAuthority::new(config).await?; - Ok(Self::IntelTA(ta_client)) - } - - pub async fn verify(&self, tee: Tee, nonce: &str, attestation: &str) -> Result { - match self { - #[cfg(feature = "coco-as-grpc")] - AttestationService::CoCoASgRPC(inner) => inner.verify(tee, nonce, attestation).await, - #[cfg(any(feature = "coco-as-builtin", feature = "coco-as-builtin-no-verifier"))] - AttestationService::CoCoASBuiltIn(inner) => inner.verify(tee, nonce, attestation).await, - #[cfg(feature = "intel-trust-authority-as")] - AttestationService::IntelTA(inner) => inner.verify(tee, nonce, attestation).await, - } - } - - pub async fn set_policy(&self, policy_id: &str, policy: &str) -> Result<()> { - match self { - #[cfg(feature = "coco-as-grpc")] - AttestationService::CoCoASgRPC(inner) => inner.set_policy(policy_id, policy).await, - #[cfg(any(feature = "coco-as-builtin", feature = "coco-as-builtin-no-verifier"))] - AttestationService::CoCoASBuiltIn(inner) => inner.set_policy(policy_id, policy).await, - #[cfg(feature = "intel-trust-authority-as")] - AttestationService::IntelTA(inner) => inner.set_policy(policy_id, policy).await, - } - } - - pub async fn generate_challenge( - &self, - tee: Tee, - tee_parameters: serde_json::Value, - ) -> Result { - match self { - #[cfg(feature = "coco-as-grpc")] - AttestationService::CoCoASgRPC(inner) => { - inner.generate_challenge(tee, tee_parameters).await - } - #[cfg(any(feature = "coco-as-builtin", feature = "coco-as-builtin-no-verifier"))] - AttestationService::CoCoASBuiltIn(inner) => { - inner.generate_challenge(tee, tee_parameters).await - } - #[cfg(feature = "intel-trust-authority-as")] - AttestationService::IntelTA(inner) => { - inner.generate_challenge(tee, tee_parameters).await - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_make_nonce() { - const BITS_PER_BYTE: usize = 8; - - /// A base-64 encoded value is this many bits in length. - const BASE64_BITS_CHUNK: usize = 6; - - /// Number of bytes that base64 encoding requires the result to align on. - const BASE64_ROUNDING_MULTIPLE: usize = 4; - - /// The nominal base64 encoded length. - const BASE64_NONCE_LENGTH_UNROUNDED_BYTES: usize = - (NONCE_SIZE_BYTES * BITS_PER_BYTE) / BASE64_BITS_CHUNK; - - /// The actual base64 encoded length is rounded up to the specified multiple. - const EXPECTED_LENGTH_BYTES: usize = - BASE64_NONCE_LENGTH_UNROUNDED_BYTES.next_multiple_of(BASE64_ROUNDING_MULTIPLE); - - // Number of nonce tests to run (arbitrary) - let nonce_count = 13; - - let mut nonces = vec![]; - - for _ in 0..nonce_count { - let nonce = make_nonce().await.unwrap(); - - assert_eq!(nonce.len(), EXPECTED_LENGTH_BYTES); - - let found = nonces.contains(&nonce); +pub mod backend; +pub mod config; +pub mod session; - // The nonces should be unique - assert_eq!(found, false); +pub use backend::AttestationService; - nonces.push(nonce); - } - } -} +pub mod error; +pub use error::*; diff --git a/kbs/src/session.rs b/kbs/src/attestation/session.rs similarity index 95% rename from kbs/src/session.rs rename to kbs/src/attestation/session.rs index ec2169d3b..50892776c 100644 --- a/kbs/src/session.rs +++ b/kbs/src/attestation/session.rs @@ -23,7 +23,6 @@ pub(crate) enum SessionStatus { }, Attested { - attestation_claims: String, token: String, id: String, timeout: OffsetDateTime, @@ -85,11 +84,10 @@ impl SessionStatus { return *self.timeout() < OffsetDateTime::now_utc(); } - pub fn attest(&mut self, attestation_claims: String, token: String) { + pub fn attest(&mut self, token: String) { match self { SessionStatus::Authed { id, timeout, .. } => { *self = SessionStatus::Attested { - attestation_claims, token, id: id.clone(), timeout: *timeout, diff --git a/kbs/src/http/attest.rs b/kbs/src/http/attest.rs deleted file mode 100644 index f4de7e87d..000000000 --- a/kbs/src/http/attest.rs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2022 by Rivos Inc. -// Licensed under the Apache License, Version 2.0, see LICENSE for details. -// SPDX-License-Identifier: Apache-2.0 - -use crate::{raise_error, session::SessionStatus}; - -use super::*; - -use anyhow::anyhow; -use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}; -use base64::Engine; -use kbs_types::Challenge; -use log::{debug, error, info}; -use semver::{BuildMetadata, Prerelease, Version, VersionReq}; -use serde_json::json; - -static KBS_MAJOR_VERSION: u64 = 0; -static KBS_MINOR_VERSION: u64 = 1; -static KBS_PATCH_VERSION: u64 = 1; - -lazy_static! { - static ref VERSION_REQ: VersionReq = { - let kbs_version = Version { - major: KBS_MAJOR_VERSION, - minor: KBS_MINOR_VERSION, - patch: KBS_PATCH_VERSION, - pre: Prerelease::EMPTY, - build: BuildMetadata::EMPTY, - }; - - VersionReq::parse(&format!("={kbs_version}")).unwrap() - }; -} - -/// POST /auth -pub(crate) async fn auth( - request: web::Json, - map: web::Data, - timeout: web::Data, - attestation_service: web::Data>, -) -> Result { - info!("Auth API called."); - debug!("Auth Request: {:?}", &request); - let version = Version::parse(&request.version).unwrap(); - if !VERSION_REQ.matches(&version) { - raise_error!(Error::ProtocolVersion(format!( - "expected version: {}, requested version: {}", - *VERSION_REQ, - request.version.clone() - ))); - } - - let challenge = attestation_service - .generate_challenge(request.tee, request.extra_params.clone()) - .await - .map_err(|e| Error::FailedAuthentication(format!("generate challenge: {e:?}")))?; - - let session = SessionStatus::auth(request.0, **timeout, challenge) - .map_err(|e| Error::FailedAuthentication(format!("Session: {e}")))?; - - let response = HttpResponse::Ok() - .cookie(session.cookie()) - .json(session.challenge()); - - map.insert(session); - - Ok(response) -} - -/// POST /attest -pub(crate) async fn attest( - attestation: web::Json, - request: HttpRequest, - map: web::Data, - attestation_service: web::Data>, -) -> Result { - info!("Attest API called."); - let cookie = request.cookie(KBS_SESSION_ID).ok_or(Error::MissingCookie)?; - - let (tee, nonce) = { - let session = map - .sessions - .get_async(cookie.value()) - .await - .ok_or(Error::InvalidCookie)?; - let session = session.get(); - - debug!("Session ID {}", session.id()); - - if session.is_expired() { - raise_error!(Error::ExpiredCookie); - } - - if let SessionStatus::Attested { token, .. } = session { - debug!( - "Session {} is already attested. Skip attestation and return the old token", - session.id() - ); - let body = serde_json::to_string(&json!({ - "token": token, - })) - .map_err(|e| Error::TokenIssueFailed(format!("Serialize token failed {e}")))?; - - return Ok(HttpResponse::Ok() - .cookie(session.cookie()) - .content_type("application/json") - .body(body)); - } - - let attestation_str = serde_json::to_string_pretty(&attestation.0) - .map_err(|_| Error::AttestationFailed("Failed to serialize Attestation".into()))?; - debug!("Attestation: {attestation_str}"); - - (session.request().tee, session.challenge().nonce.to_string()) - }; - - let attestation_str = serde_json::to_string(&attestation) - .map_err(|e| Error::AttestationFailed(format!("serialize attestation failed : {e:?}")))?; - let token = attestation_service - .verify(tee, &nonce, &attestation_str) - .await - .map_err(|e| Error::AttestationFailed(format!("{e:?}")))?; - - let claims_b64 = token - .split('.') - .nth(1) - .ok_or_else(|| Error::TokenIssueFailed("Illegal token format".to_string()))?; - let claims = String::from_utf8( - URL_SAFE_NO_PAD - .decode(claims_b64) - .map_err(|e| Error::TokenIssueFailed(format!("Illegal token base64 claims: {e}")))?, - ) - .map_err(|e| Error::TokenIssueFailed(format!("Illegal token base64 claims: {e}")))?; - - let mut session = map - .sessions - .get_async(cookie.value()) - .await - .ok_or(Error::InvalidCookie)?; - let session = session.get_mut(); - - let body = serde_json::to_string(&json!({ - "token": token, - })) - .map_err(|e| Error::TokenIssueFailed(format!("Serialize token failed {e}")))?; - - session.attest(claims, token); - - Ok(HttpResponse::Ok() - .cookie(session.cookie()) - .content_type("application/json") - .body(body)) -}