Skip to content

Commit

Permalink
feat(server): Use Rego to appraise EARs
Browse files Browse the repository at this point in the history
Use Rego to implement the appraisal policy for attestation results.

A default policy is hardcoded (see keybroker-server/src/policy.rego).

The user may provide their own policy to override the default using the
`--policy` command line switch.

The user must provide a JSON file containing one or more RIM reference values.

Fix #13

Signed-off-by: Thomas Fossati <thomas.fossati@linaro.org>
  • Loading branch information
thomas-fossati committed Sep 25, 2024
1 parent f8bc788 commit ae0361f
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 10 deletions.
3 changes: 3 additions & 0 deletions rust-keybroker/keybroker-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ clap = { version = "=4.3.24", features = ["derive", "std"] }
keybroker-common = { path = "../keybroker-common" }
veraison-apiclient = { git = "https://github.com/veraison/rust-apiclient.git" }
ear = { git = "https://github.com/veraison/rust-ear.git" }
regorus = "0.2.5"
serde_json = "1.0.128"
anyhow = "1.0.89"
8 changes: 8 additions & 0 deletions rust-keybroker/keybroker-server/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ pub enum Error {
/// that are transacted through the API between the client and the server, if the client provides faulty data.
#[error(transparent)]
Base64Decode(#[from] base64::DecodeError),

/// Represents errors from the use of the policy evaluation library.
#[error(transparent)]
Policy(#[from] anyhow::Error),

/// Represents errors from the use of the JSON serialisation and deserialisation library.
#[error(transparent)]
Json(#[from] serde_json::Error),
}

/// Errors happening within the verification process logic.
Expand Down
14 changes: 14 additions & 0 deletions rust-keybroker/keybroker-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use keystore::KeyStore;
mod challenge;
mod error;
mod keystore;
pub mod policy;
mod verifier;

#[post("/key/{keyid}")]
Expand Down Expand Up @@ -90,6 +91,9 @@ async fn submit_evidence(

let verifier_base = data.args.verifier.clone();

let optional_policy = data.args.policy.clone();
let rims = data.args.rims.clone();

// We are in an async context, but the verifier client is synchronous, so spawn
// it as a blocking task.
let handle = task::spawn_blocking(move || {
Expand All @@ -102,6 +106,8 @@ async fn submit_evidence(
content_type_str,
&challenge.challenge_value,
&evidence_bytes,
optional_policy,
&rims,
)
});
let result = handle.await.unwrap();
Expand Down Expand Up @@ -167,6 +173,14 @@ struct Args {
/// Set the server verbosity
#[arg(short, long, default_value_t = false)]
verbose: bool,

/// Rego file containing the appraisal policy for (EAR) attestation results
#[arg(long, default_value = None)]
policy: Option<String>,

/// File containing a JSON array with base64-encoded known-good RIM values
#[arg(long, default_value = "rims.json")]
rims: String,
}

struct ServerState {
Expand Down
23 changes: 23 additions & 0 deletions rust-keybroker/keybroker-server/src/policy.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package arm_cca

default allow := false

allow if {
input.eat_profile == "tag:github.com,2023:veraison/ear"

# platform part
prec := input.submods.CCA_SSD_PLATFORM
prec["ear.status"] == "affirming"

# realm part
rrec := input.submods.CCA_REALM
rrec["ear.status"] == "warning"

rtv := rrec["ear.trustworthiness-vector"]
rtv["instance-identity"] == 2

# check RIM value against known-good-values
rclaims := rrec["ear.veraison.annotated-evidence"]
rim := rclaims["cca-realm-initial-measurement"]
rim in data["reference-values"]
}
75 changes: 75 additions & 0 deletions rust-keybroker/keybroker-server/src/policy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2024 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

use regorus::{self, Value};

use crate::error::Result;

// Evaluate an EAR claims-set against the appraisal policy and known-good RIM values
pub(crate) fn rego_eval(
custom_policy: Option<String>,
rims: &str,
ear_claims: &str,
) -> Result<Value> {
const POLICY: &str = include_str!("policy.rego");

// Create engine.
let mut engine = regorus::Engine::new();

engine.set_rego_v1(true);
engine.set_strict_builtin_errors(false);

// Add the appraisal policy
match custom_policy {
None => {
engine.add_policy(String::from("policy.rego"), String::from(POLICY))?;
}
Some(file) => {
engine.add_policy_from_file(file)?;
}
}

// Load the configured known good RIM values
engine.add_data(Value::from_json_file(rims)?)?;

// Set the EAR claims-set to be appraised
engine.set_input(Value::from_json_str(ear_claims)?);

let results = engine.eval_rule("data.arm_cca.allow".to_string())?;

Ok(results)
}

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

#[test]
fn rego_eval_ear_default_policy_ok() {
let ear_claims = include_str!("../../../testdata/ear-claims-ok.json");
let rims = stringify_testdata_path("rims-matching.json");

let results = rego_eval(None, &rims, ear_claims).expect("successful eval");

assert_eq!(results.to_string(), "true");
}

#[test]
fn rego_eval_default_policy_unmatched_rim() {
let ear_claims = include_str!("../../../testdata/ear-claims-ok.json");
let rims = stringify_testdata_path("rims-not-matching.json");

let results = rego_eval(None, &rims, ear_claims).expect("successful eval");

assert_eq!(results.to_string(), "false");
}

fn stringify_testdata_path(s: &str) -> String {
let mut test_data = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));

test_data.push("../../testdata");
test_data.push(s);

test_data.into_os_string().into_string().unwrap()
}
}
22 changes: 12 additions & 10 deletions rust-keybroker/keybroker-server/src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
// SPDX-License-Identifier: Apache-2.0

use crate::error::Result;
use ear::{Algorithm, Ear, TrustTier};
use crate::policy;
use ear::{Algorithm, Ear};
use veraison_apiclient::*;

pub fn verify_with_veraison_instance(
verifier_base_url: &str,
media_type: &str,
challenge: &[u8],
evidence: &[u8],
policy: Option<String>,
rims: &str,
) -> Result<bool> {
// Get the discovery URL from the base URL
let discovery = Discovery::from_base_url(String::from(verifier_base_url))?;
Expand Down Expand Up @@ -60,14 +63,13 @@ pub fn verify_with_veraison_instance(
verification_key_string.as_bytes(),
)?;

// The simplest possible appraisal policy: accept if we have an AFFIRMING or WARNING result
// from every submodule.
// TODO: This policy is rather too "relaxed" - the simplest and strictest policy would be
// to require AFFIRMING from every submodule. We have some integration issues with Veraison
// today that prevent this.
let verified = ear.submods.iter().all(|(_module, appraisal)| {
appraisal.status == TrustTier::Affirming || appraisal.status == TrustTier::Warning
});
let ear_claims = serde_json::to_string(&ear)?;

Ok(verified)
// Appraise the received EAR using the embedded policy (see ./policy.rego)
// unless a custom one has been provided on the command line. The deafault
// policy also wants to match the RIM value reported by the CCA token with
// the known-good RIM values supplied on the command line.
let results = policy::rego_eval(policy, rims, &ear_claims)?;

Ok(results.to_string() == "true")
}
36 changes: 36 additions & 0 deletions testdata/ear-claims-ok.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"eat_nonce": "bobW2XzHE7xt1D285JGmtAMRwCeov4WjnaY-nORMEyqKEZ0pb65qaZnpvz5EcbDOASRdiJQkwx6JeTs7HWsVBA==",
"eat_profile": "tag:github.com,2023:veraison/ear",
"submods": {
"CCA_REALM": {
"ear.status": "warning",
"ear.trustworthiness-vector": {
"configuration": 0,
"executables": 33,
"file-system": 0,
"hardware": 0,
"instance-identity": 2,
"runtime-opaque": 0,
"sourced-data": 0,
"storage-opaque": 0
},
"ear.veraison.annotated-evidence": {
"cca-realm-challenge": "bobW2XzHE7xt1D285JGmtAMRwCeov4WjnaY+nORMEyqKEZ0pb65qaZnpvz5EcbDOASRdiJQkwx6JeTs7HWsVBA==",
"cca-realm-extensible-measurements": [
"JNWwopbMBcvYBoxQZ8W9Rzt3Ddpq4IL+O6MKvj+aarE=",
"eI/AkL/GuO2QMVK6hBTnPa9bjHux55rVAqsGmbZZ7RY=",
"2sRqWEFdw6ANenQYUgCOnK5k9S0DufdtdvSzZE/vxBY=",
"MsavxiflVYXAMVU1nzMaDiJfaEDblH3Zbvq4G+JnGTk="
],
"cca-realm-hash-algo-id": "sha-256",
"cca-realm-initial-measurement": "MRMUq3NiA1DPdYg0rlxl2ejC3H/r5ufZZUu+hk4wDUk=",
"cca-realm-personalization-value": "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIDEzIGxhenkgZG9ncy5UaGUgcXVpY2sgYnJvd24gZm94IA==",
"cca-realm-public-key": "BHb5iAkb5YXtQYAa7Pq4WFSMYwV+FrDmdhILvQ0vnCngVsXUGgEw65whUXiZ3CMUayjhsGK9PqSzFf0hnxy7Uoy250ykm+Fnc3NPYaHKYQMbK789kY8vlP/EIo5QkZVErg==",
"cca-realm-public-key-hash-algo-id": "sha-256"
}
},
"CCA_SSD_PLATFORM": {
"ear.status": "affirming"
}
}
}
6 changes: 6 additions & 0 deletions testdata/rims-matching.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"reference-values": [
"MRMUq3NiA1DPdYg0rlxl2ejC3H/r5ufZZUu+hk4wDUk=",
"q3N/r5ufZZUu+iAg0rlxl2ejC3HMRMUhk4wDUk1DPdY="
]
}
6 changes: 6 additions & 0 deletions testdata/rims-not-matching.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"reference-values": [
"XRMUq3NiA1DPdYg0rlxl2ejC3H/r5ufZZUu+hk4wDUk=",
"X3N/r5ufZZUu+iAg0rlxl2ejC3HMRMUhk4wDUk1DPdY="
]
}

0 comments on commit ae0361f

Please sign in to comment.