Skip to content

Commit

Permalink
token: implement EAR token generation
Browse files Browse the repository at this point in the history
This commit allows the AS to issue EAR tokens with the
help of the rust-ear crate.

EAR tokens require particular claims. This creates a binding
between the AS policy and the EAR token.
Specifically, the policy engine must return an EAR appraisal.
The policy engine is still generic. Multiple policy engines
could be implemented as long as they create an appraisal.

Token generation is no longer generic.
Since the policy engine, will always return an appraisal,
we must generate an EAR token.
This commit removes the simple token issuer
and replaces the TokenProvider trait with a struct.

The KBS will still be able to validate many different tokens,
but this commit changes the AS to only issue EAR tokens.

There are a few other changes, including that the policy engine
no longer takes multiple policies. For now, we only evaluate
the first policy in the policy list, but future commits will
change this convention so that we only ever think about one
policy for the attestation service (until we introduce support
for validating multiple devices at once).

This commit also slightly changes how we handle init-data by
adding the init_data_claims and runtime_data_claims
to the flattened claims when the init_data and report_data
fields are part of the tcb_claims returned by the verifier.

This surfaces the json init_data and report_data to the AS
and KBS policy engines and includes them in the EAR token.
Note that this will increase the size of the token
and that some complex init_data values might break out
JSON flattening code.

Signed-off-by: Tobin Feldman-Fitzthum <[email protected]>
  • Loading branch information
fitzthum committed Sep 28, 2024
1 parent 40f9b6f commit 93bce72
Show file tree
Hide file tree
Showing 12 changed files with 370 additions and 498 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/as-rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:

- name: Install OPA command line tool
run: |
curl -L -o opa https://openpolicyagent.org/downloads/v0.42.2/opa_linux_amd64_static
curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static
chmod 755 ./opa && cp opa /usr/local/bin
- name: OPA policy.rego fmt and check
Expand Down
22 changes: 20 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ cfg-if = "1.0.0"
chrono = "0.4.19"
clap = { version = "4", features = ["derive"] }
config = "0.13.3"
#ear = "0.2.0"
ear = { git = "https://github.com/veraison/rust-ear.git", rev = "13962e36225f85dcc3bad8ac7047a306b1350baf" }
env_logger = "0.10.0"
hex = "0.4.3"
jwt-simple = "0.11"
Expand Down
1 change: 1 addition & 0 deletions attestation-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ async-trait.workspace = true
base64.workspace = true
cfg-if.workspace = true
clap = { workspace = true, optional = true }
ear.workspace = true
env_logger = { workspace = true, optional = true }
futures = "0.3.17"
hex.workspace = true
Expand Down
9 changes: 1 addition & 8 deletions attestation-service/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::rvps::RvpsConfig;
use crate::token::{AttestationTokenBrokerType, AttestationTokenConfig};
use crate::token::AttestationTokenConfig;

use serde::Deserialize;
use std::fs::File;
Expand All @@ -21,12 +21,6 @@ pub struct Config {
/// Configurations for RVPS.
pub rvps_config: RvpsConfig,

/// The Attestation Result Token Broker type.
///
/// Possible values:
/// * `Simple`
pub attestation_token_broker: AttestationTokenBrokerType,

/// The Attestation Result Token Broker Config
pub attestation_token_config: AttestationTokenConfig,
}
Expand Down Expand Up @@ -54,7 +48,6 @@ impl Default for Config {
work_dir,
policy_engine: "opa".to_string(),
rvps_config: RvpsConfig::default(),
attestation_token_broker: AttestationTokenBrokerType::Simple,
attestation_token_config: AttestationTokenConfig::default(),
}
}
Expand Down
93 changes: 46 additions & 47 deletions attestation-service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ pub use kbs_types::{Attestation, Tee};
use log::{debug, info};
use policy_engine::{PolicyEngine, PolicyEngineType};
use rvps::{RvpsApi, RvpsError};
use serde_json::{json, Value};
use serde_json::Value;
use serde_variant::to_variant_name;
use sha2::{Digest, Sha256, Sha384, Sha512};
use std::{collections::HashMap, str::FromStr};
use std::{
collections::{BTreeMap, HashMap},
str::FromStr,
};
use strum::{AsRefStr, Display, EnumString};
use thiserror::Error;
use tokio::fs;
Expand Down Expand Up @@ -96,7 +99,7 @@ pub struct AttestationService {
_config: Config,
policy_engine: Box<dyn PolicyEngine + Send + Sync>,
rvps: Box<dyn RvpsApi + Send + Sync>,
token_broker: Box<dyn AttestationTokenBroker + Send + Sync>,
token_broker: AttestationTokenBroker,
}

impl AttestationService {
Expand All @@ -116,9 +119,7 @@ impl AttestationService {
.await
.map_err(ServiceError::Rvps)?;

let token_broker = config
.attestation_token_broker
.to_token_broker(config.attestation_token_config.clone())?;
let token_broker = AttestationTokenBroker::new(config.attestation_token_config.clone())?;

Ok(Self {
_config: config,
Expand Down Expand Up @@ -164,9 +165,8 @@ impl AttestationService {
/// will not be performed.
/// - `hash_algorithm`: The hash algorithm that is used to calculate the digest of `runtime_data` and
/// `init_data`.
/// - `policy_ids`: The policy ids that used to check this evidence. Any check fails against a policy will
/// not cause this function to return error. The result check against every policy will be included inside
/// the finally Token returned by CoCo-AS.
/// - `policy_id`: The id of the policy that will be used to evaluate the claims.
/// The hash of the policy will be returned as part of the attestation token.
#[allow(clippy::too_many_arguments)]
pub async fn evaluate(
&self,
Expand Down Expand Up @@ -196,62 +196,61 @@ impl AttestationService {
None => InitDataHash::NotProvided,
};

let claims_from_tee_evidence = verifier
let mut claims_from_tee_evidence = verifier
.evaluate(&evidence, &report_data, &init_data_hash)
.await
.map_err(|e| anyhow!("Verifier evaluate failed: {e:?}"))?;
info!("{:?} Verifier/endorsement check passed.", tee);

let flattened_claims = flatten_claims(tee, &claims_from_tee_evidence)?;
debug!("flattened_claims: {:#?}", flattened_claims);
// If the verifier produces an init_data claim (meaning that
// it has validated the init_data hash), add the JSON init_data_claims,
// to the claims map. Do the same for the report data.
//
// These claims will be flattened and provided to the policy engine.
// They will also end up in the EAR token as part of the annotated evidence.
if let Some(claims_map) = claims_from_tee_evidence.as_object_mut() {
if claims_map.get("init_data").is_some() {
claims_map.insert("init_data_claims".to_string(), init_data_claims);
}

if claims_map.get("report_data").is_some() {
claims_map.insert("report_data_claims".to_string(), runtime_data_claims);
}
}

let mut flattened_claims = BTreeMap::new();
flatten_claims(
&mut flattened_claims,
&claims_from_tee_evidence,
to_variant_name(&tee)?.to_string(),
)?;

let tcb_json = serde_json::to_string(&flattened_claims)?;
debug!("flattened_claims: {:#?}", flattened_claims);

let reference_data_map = self
.get_reference_data(flattened_claims.keys())
.await
.map_err(|e| anyhow!("Generate reference data failed: {:?}", e))?;
debug!("reference_data_map: {:#?}", reference_data_map);

let evaluation_report = self
let appraisal = self
.policy_engine
.evaluate(reference_data_map.clone(), tcb_json, policy_ids.clone())
.evaluate(
reference_data_map.clone(),
flattened_claims,
policy_ids[0].clone(),
)
.await
.map_err(|e| anyhow!("Policy Engine evaluation failed: {e}"))?;

info!("Policy check passed.");
let policies: Vec<_> = evaluation_report
.into_iter()
.map(|(k, v)| {
json!({
"policy-id": k,
"policy-hash": v,
})
})
.collect();

let reference_data_map: HashMap<String, Vec<String>> = reference_data_map
.into_iter()
.filter(|it| !it.1.is_empty())
.collect();

let token_claims = json!({
"tee": to_variant_name(&tee)?,
"evaluation-reports": policies,
"tcb-status": flattened_claims,
"reference-data": reference_data_map,
"customized_claims": {
"init_data": init_data_claims,
"runtime_data": runtime_data_claims,
},
});

let attestation_results_token = self.token_broker.issue(token_claims)?;
info!(
"Attestation Token ({}) generated.",
self._config.attestation_token_broker
);
info!("TCB Appraisal Generated Successfully");

// For now, create only one submod, called `cpu`.
// We can create more when we support attesting multiple devices at once.
let mut submods = BTreeMap::new();
submods.insert("cpu".to_string(), appraisal);

let attestation_results_token = self.token_broker.issue_ear(submods)?;
Ok(attestation_results_token)
}

Expand Down
19 changes: 9 additions & 10 deletions attestation-service/src/policy_engine/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use anyhow::Result;
use async_trait::async_trait;
use ear::{Appraisal, RawValue};
use serde::Deserialize;
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};
use std::io;
use std::path::Path;
use strum::EnumString;
Expand Down Expand Up @@ -41,6 +42,8 @@ pub enum PolicyError {
EvalPolicyFailed(#[source] anyhow::Error),
#[error("json serialization failed: {0}")]
JsonSerializationFailed(#[source] anyhow::Error),
#[error("Policy claim value not valid (must be between -127 and 127)")]
InvalidClaimValue,
}

#[derive(Debug, EnumString, Deserialize)]
Expand All @@ -62,18 +65,14 @@ type PolicyDigest = String;

#[async_trait]
pub trait PolicyEngine {
/// Verify an input body against a set of ref values and a list of policies
/// return a list of policy ids with their sha384 at eval time
/// abort early on first failed validation and any errors.
/// The result is a key-value map.
/// - `key`: the policy id
/// - `value`: the digest of the policy (using **Sha384**).
/// Verify an input body against a set of ref values and a policy id
/// return an EAR Appraisal
async fn evaluate(
&self,
reference_data_map: HashMap<String, Vec<String>>,
input: String,
policy_ids: Vec<String>,
) -> Result<HashMap<String, PolicyDigest>, PolicyError>;
tcb_claims: BTreeMap<String, RawValue>,
policy_id: String,
) -> Result<Appraisal, PolicyError>;

async fn set_policy(&mut self, policy_id: String, policy: String) -> Result<(), PolicyError>;

Expand Down
Loading

0 comments on commit 93bce72

Please sign in to comment.