Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,426 changes: 1,703 additions & 723 deletions Cargo.lock

Large diffs are not rendered by default.

48 changes: 26 additions & 22 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,34 @@ name = "cover-cli"
path = "src/bin/main.rs"

[dependencies]
anyhow = "1.0.97"
base64 = "0.22.1"
ccatoken = { git = "https://github.com/veraison/rust-ccatoken", rev = "6d5b8db9" }
anyhow = "1.0.104"
base64 = "0.23.1"
ccatoken = { git = "https://github.com/veraison/rust-ccatoken", rev = "870c83f" }
ciborium = "0.2.2"
clap = { version = "4.5.32", features = ["derive"] }
clap-verbosity-flag = "3.0.3"
corim-rs = { git = "https://github.com/veraison/corim-rs", features = ["openssl"] }
cose-rust = { version = "0.1.7", features = ["serde_json"] }
ear = { git = "https://github.com/veraison/rust-ear", rev = "15184e9a" }
env_logger = { version = "0.11.8", features = ["kv"] }
jsonwebtoken = "9.3.1"
log = { version = "0.4.27", features = ["kv", "std"] }
regorus = "0.4.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = { version = "1.0.140", features = ["raw_value"] }
pem = "3.0.6"
picky-asn1-der = "0.5.4"
picky-asn1-x509 = "0.15.2"
elliptic-curve = { version = "0.13.8", features = ["arithmetic"] }
p256 = "0.13.2"
p384 = "0.13.1"
p521 = "0.13.3"
clap = { version = "4.6.3", features = ["derive"] }
clap-verbosity-flag = "3.0.4"
cmw = "0.1.2"
corim-rs = { version = "0.2.0", features = ["openssl"] }
cose-rust = { version = "0.1.8", features = ["serde_json"] }
ear = { git = "https://github.com/veraison/rust-ear", rev = "084529d" }
env_logger = { version = "0.11.11", features = ["kv"] }
jsonwebtoken = "10.3.0"
log = { version = "0.4.33", features = ["kv", "std"] }
regorus = "0.11.0"
serde = { version = "1.0.229", features = ["derive"] }
serde_json = { version = "1.0.151", features = ["raw_value"] }
pem = "4.0.0"
picky-asn1-der = "0.5.6"
picky-asn1-x509 = "0.15.4"
elliptic-curve = { version = "0.14.1", features = ["arithmetic"] }
p256 = "0.14.0"
p384 = "0.14.0"
p521 = "0.14.0"
chrono = { version = "0.4", features = ["serde"] }
strum = "0.28.0"
strum_macros = "0.28.0"

[dev-dependencies]
ciborium = "0.2.2"
serde_json = "1.0.140"
serde_json = "1.0.151"
test-case = "3.3.1"
32 changes: 20 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,45 @@
# cover

Cover (COrim VERifier) is an implementation of CoRIM-based verifier as outline in CoRIM draft
spec (rev 8.) Section 9\[[1]\]. It attempts follow the outlined algorithm up to phase 4 (ACS
generation). In lieu of subsequent phases, it uses a Rego-based policy engine for policy
spec (rev 11) Section 8\[[1]\]. It follows the outlined algorithm up to phase 4 (ACS generation).
In lieu of subsequent phases, it uses a Rego-based policy engine for policy
evaluation, and generates an attestation result in EAR\[[2]\] format.

This implementation is intended as a Proof-of-Concept only. It has the following limitations:

- Arm CCA is the only attestation scheme that is currently implemented.
- Only signed CoRIMs are supported.

- Only basic in-memory implementation of key and CoRIM stores are implemented.

The verification flow proceeds as follows.

- CoRIMs are processed by validating their signatures and extracting contained measurements
into the "corim store" as RV (reference values), EV (endorsed values), and EVS (endorsed
values series) relations.
- CoRIMs are processed by validating their signatures for signed CoRIMs and extracting contained
measurements into the "corim store" as RV (reference values), EV (endorsed values) or Key relations.
- Unsigned CoRIM verification is not done and it is assumed that user has already verified the
CoRIMs before passing into the verifier.
- The signature on the evidence is verified using a trust anchor obtained from the corim store
based on an identifier inside the evidence. This is scheme-specific. For CCA, the instance ID
is used. Evidence claims are then extracted as ECT (environment-claims tuple) records.
- The evidence ECTs are then matched to the relations in the corim store. This results in the
ACS (appraisal claims set) -- a vector of ECT records containing evidence claims and matched

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct. -- in ASCII indicates an em-dash (rather than a hypen), which is what is intended here.

ACS (appraisal claims set) — a vector of ECT records containing evidence claims and matched
reference values and endorsements.
- The ACS is used as an input into the policy engine along with scheme-specific policies. Each
policy results in an appraisal containing an AR4SI\[[3]\] trust vector.
- The appraisals are added to an attestation result in EAR\[[2]\] format.

[1]: https://www.ietf.org/archive/id/draft-ietf-rats-corim-08.html#name-example-verifier-algorithm
[2]: https://www.ietf.org/archive/id/draft-fv-rats-ear-05.html
[3]: https://www.ietf.org/archive/id/draft-ietf-rats-ar4si-09.html
[1]: https://www.ietf.org/archive/id/draft-ietf-rats-corim-11.html#name-reference-verifier
[2]: https://www.ietf.org/archive/id/draft-ietf-rats-ear-04.html
[3]: https://www.ietf.org/archive/id/draft-ietf-rats-ar4si-10.html


## API

Verification flow consists of the following components:

- A key store that contains keys that are used to verify signatures on CoRIMs. The key for a
CoRIM is looked up from the store based on the `kid` inside the CoRIM.
For unsigned CoRIMs, it is assumed that CoRIM is already verified by user and user provided
pub key is used as verfying authority of the CoRIM.
- A CoRIM store that loads endorsements and reference values from CoRIMs.
- A scheme that defines how evidence is processed to extract claims, and what policy is applied
to create an attestation result.
Expand Down Expand Up @@ -90,9 +95,12 @@ Verification flow consists of the following components:

```bash
target/debug/cover-cli --corim-dir test/corim/ \
--key test/corim/key.pub.pem --pretty test/cca/cca-token-01.cbor \
# Public key used to verify signed CoRIM signatures
--key test/corim/key.pub.pem
# For unsigned corim and attest/identity key, this is used as verifying authority
--verifier-key test/corim/key.pub.pem \
--pretty test/cca/cca-token-01.cbor \
--nonce adfadaewafewr32r --output cca-token-01.ear.json
```

use `-h` to see the full list of command line arguments.

3 changes: 3 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ allow = [
#"Apache-2.0 WITH LLVM-exception",
# Considered Copyleft, but permitted in this project
"MPL-2.0",
# BlueOak is an OSI approved licence,
# https://opensource.org/license/BlueOak-1.0.0
"BlueOak-1.0.0",
]
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
Expand Down
61 changes: 54 additions & 7 deletions src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use base64::{
};
use clap::{ArgAction, Parser};
use clap_verbosity_flag::{InfoLevel, Verbosity};
use corim_rs::Corim;
use log::{debug, error, info};

use cover::{
Expand Down Expand Up @@ -40,6 +41,11 @@ struct Cli {
#[arg(name = "key", short, long, action = ArgAction::Append)]
keys: Vec<String>,

/// Public key of Verifier/user of library in PEM format. This key is used as authority of attest/identiy key
/// during internal processing and if unsigned corim is provided then same key is used as authority for CoRIMs.
#[arg(long = "verifier-key")]
verifier_key: String,

/// Path to CoRIM containing data relevant to verification of provided evidence.
#[arg(short, long = "corim", action = ArgAction::Append)]
corims: Vec<String>,
Expand Down Expand Up @@ -93,7 +99,6 @@ fn read_key<P: AsRef<Path>>(path: P) -> Result<(String, Vec<u8>)> {
2 => Ok((parts[0].to_string(), parts[1].to_string())),
_ => Err(Error::custom("invalid key path")),
}?;

let bytes = fs::read(&actual_path).map_err(Error::custom)?;

Ok((kid, bytes))
Expand All @@ -118,33 +123,71 @@ fn verify(args: &Cli) -> Result<()> {
let mut key_store = MemKeyStore::new();

for key_path in &args.keys {
debug!("reading key from {:?}", key_path);
debug!("reading CoRIM verification key from \"{}\"", key_path);
let (kid, key) = read_key(key_path)?;
key_store.add(kid.as_bytes(), key.as_ref())?;
}

debug!("reading user/verfier key from \"{}\"", args.verifier_key);
let (_, verifier_key) = read_key(&args.verifier_key)?;
key_store.add("verifier-key".as_bytes(), &verifier_key)?;

let mut corim_store = MemCorimStore::new(key_store);

let mut corim_loaded = false;

for corim in &args.corims {
debug!("loading CoRIM {:?}", corim);
let corim_bytes = fs::read(corim).map_err(Error::custom)?;
corim_store.add_bytes(corim_bytes.as_slice())?;
let parsed_corim = Corim::from_cbor(corim_bytes.as_slice())?;
if schemes.values().any(|scheme| {
scheme
.as_ref()
.supports_corim(&parsed_corim)
.unwrap_or(false)
}) {
corim_store.add(&parsed_corim)?;
corim_loaded = true;
} else {
info!(
"skipping CoRIM {:?} because it does not match a supported scheme profile or is expired",
corim
);
}
}

for dir in &args.corim_dirs {
for entry in fs::read_dir(dir)? {
let entry = entry?;
match entry.path().extension().and_then(OsStr::to_str) {
Some("cbor") | Some("corim") => {
debug!("loading CoRIM {:?}", entry.path());
info!("loading CoRIM {:?}", entry.path());
let corim_bytes = fs::read(entry.path()).map_err(Error::custom)?;
corim_store.add_bytes(corim_bytes.as_slice())?;
let parsed_corim = Corim::from_cbor(corim_bytes.as_slice())?;
if schemes.values().any(|scheme| {
scheme
.as_ref()
.supports_corim(&parsed_corim)
.unwrap_or(false)
}) {
corim_store.add(&parsed_corim)?;
corim_loaded = true;
} else {
info!(
"skipping CoRIM {:?} because it does not match a supported scheme profile or is expired",
entry.path()
);
};
}
Some(_) | None => (),
}
};
}
}

if !corim_loaded {
return Err(Error::custom("No valid corim found. Exiting ..."));
}

let nonce = match &args.nonce {
Some(encoded) => {
let encoded = encoded.trim_end_matches("=");
Expand All @@ -169,6 +212,10 @@ fn verify(args: &Cli) -> Result<()> {
debug!("nonce: {:x?}", nonce);

let verifier = Verifier::new(corim_store, schemes);
// Check if evidence format matches with supported schemes.
if verifier.match_evidence(evidence.as_slice()).is_none() {
return Err(Error::custom("evidence format not supported"));
}
let result = verifier.verify(&args.scheme, evidence.as_slice(), nonce.as_deref())?;

debug!("ACS: {}", serde_json::to_string(&result.acs)?);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove this?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, deleted while removing other debugging statements. Will add it back.

Expand All @@ -191,7 +238,7 @@ fn verify(args: &Cli) -> Result<()> {
}
};

info!("writing result to {}", &out_path);
info!("writing result to {}", out_path);

let mut out = match args.force {
true => File::create(&out_path),
Expand Down
37 changes: 20 additions & 17 deletions src/lib/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,26 @@ fn jwk_public_key_use_to_cose(key_use: jwk::PublicKeyUse) -> Result<CoseKeyOpera
}
}

fn jwk_algorithm_to_cose(alg: jwk::KeyAlgorithm) -> CoseAlgorithm {
fn jwk_algorithm_to_cose(alg: jwk::KeyAlgorithm) -> Result<CoseAlgorithm, Error> {
match alg {
jwk::KeyAlgorithm::HS256 => CoseAlgorithm::Hmac256_256,
jwk::KeyAlgorithm::HS384 => CoseAlgorithm::Hmac384_384,
jwk::KeyAlgorithm::HS512 => CoseAlgorithm::Hmac512_512,
jwk::KeyAlgorithm::ES256 => CoseAlgorithm::ES256,
jwk::KeyAlgorithm::ES384 => CoseAlgorithm::ES384,
jwk::KeyAlgorithm::RS256 => CoseAlgorithm::RS256,
jwk::KeyAlgorithm::RS384 => CoseAlgorithm::RS384,
jwk::KeyAlgorithm::RS512 => CoseAlgorithm::RS512,
jwk::KeyAlgorithm::PS256 => CoseAlgorithm::PS256,
jwk::KeyAlgorithm::PS384 => CoseAlgorithm::PS384,
jwk::KeyAlgorithm::PS512 => CoseAlgorithm::PS512,
jwk::KeyAlgorithm::EdDSA => CoseAlgorithm::EdDSA,
jwk::KeyAlgorithm::RSA1_5 => CoseAlgorithm::RS1,
jwk::KeyAlgorithm::RSA_OAEP => CoseAlgorithm::RsaesOaepRfc,
jwk::KeyAlgorithm::RSA_OAEP_256 => CoseAlgorithm::RsaesOaepSha256,
jwk::KeyAlgorithm::HS256 => Ok(CoseAlgorithm::Hmac256_256),
jwk::KeyAlgorithm::HS384 => Ok(CoseAlgorithm::Hmac384_384),
jwk::KeyAlgorithm::HS512 => Ok(CoseAlgorithm::Hmac512_512),
jwk::KeyAlgorithm::ES256 => Ok(CoseAlgorithm::ES256),
jwk::KeyAlgorithm::ES384 => Ok(CoseAlgorithm::ES384),
jwk::KeyAlgorithm::RS256 => Ok(CoseAlgorithm::RS256),
jwk::KeyAlgorithm::RS384 => Ok(CoseAlgorithm::RS384),
jwk::KeyAlgorithm::RS512 => Ok(CoseAlgorithm::RS512),
jwk::KeyAlgorithm::PS256 => Ok(CoseAlgorithm::PS256),
jwk::KeyAlgorithm::PS384 => Ok(CoseAlgorithm::PS384),
jwk::KeyAlgorithm::PS512 => Ok(CoseAlgorithm::PS512),
jwk::KeyAlgorithm::EdDSA => Ok(CoseAlgorithm::EdDSA),
jwk::KeyAlgorithm::RSA1_5 => Ok(CoseAlgorithm::RS1),
jwk::KeyAlgorithm::RSA_OAEP => Ok(CoseAlgorithm::RsaesOaepRfc),
jwk::KeyAlgorithm::RSA_OAEP_256 => Ok(CoseAlgorithm::RsaesOaepSha256),
jwk::KeyAlgorithm::UNKNOWN_ALGORITHM => {
Err(Error::Custom(format!("Unknowm algorithm {}", alg)))
}
}
}

Expand Down Expand Up @@ -107,7 +110,7 @@ pub fn jwk_to_crypto_key(jwk: jwk::Jwk) -> Result<CryptoKeyTypeChoice<'static>,
}

if let Some(alg) = &jwk.common.key_algorithm {
cose_key.alg = Some(jwk_algorithm_to_cose(*alg))
cose_key.alg = Some(jwk_algorithm_to_cose(*alg)?)
}

match &jwk.algorithm {
Expand Down
Loading
Loading