Skip to content

Commit

Permalink
Merge pull request #5 from paulhowardarm/phServerSkeleton
Browse files Browse the repository at this point in the history
First functional chunk of server code
  • Loading branch information
thomas-fossati authored Aug 21, 2024
2 parents 5c321e7 + 52008f2 commit d3a6950
Show file tree
Hide file tree
Showing 10 changed files with 753 additions and 12 deletions.
7 changes: 7 additions & 0 deletions rust-keybroker/keybroker-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
name = "keybroker-app"
version = "0.1.0"
edition = "2021"
authors = ["Veraison Project Contributors"]
description = "Simple command-line application demonstrating the keybroker."
license = "Apache-2.0"
repository = "https://github.com/veraison/keybroker-demo"
readme = "README.md"
keywords = ["security", "service", "attestation"]
categories = ["cryptography", "hardware-support"]

[dependencies]
keybroker-client = { path = "../keybroker-client" }
7 changes: 7 additions & 0 deletions rust-keybroker/keybroker-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
name = "keybroker-client"
version = "0.1.0"
edition = "2021"
authors = ["Veraison Project Contributors"]
description = "Rust client library for the demo keybroker."
license = "Apache-2.0"
repository = "https://github.com/veraison/keybroker-demo"
readme = "README.md"
keywords = ["security", "service", "attestation"]
categories = ["cryptography", "hardware-support"]

[dependencies]
keybroker-common = { path = "../keybroker-common" }
9 changes: 9 additions & 0 deletions rust-keybroker/keybroker-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,14 @@
name = "keybroker-common"
version = "0.1.0"
edition = "2021"
authors = ["Veraison Project Contributors"]
description = "Common API data types used by both server and client for the demo keybroker."
license = "Apache-2.0"
repository = "https://github.com/veraison/keybroker-demo"
readme = "README.md"
keywords = ["security", "service", "attestation"]
categories = ["cryptography", "hardware-support"]

[dependencies]
serde = { version = "1.0.204", features = ["serde_derive"] }
serde_with = "3.9.0"
98 changes: 88 additions & 10 deletions rust-keybroker/keybroker-common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,95 @@
// Copyright 2024 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

pub fn add(left: usize, right: usize) -> usize {
left + right
//! This library provides the common data types that are defined in the OpenAPI schema for the keybroker,
//! along with the serialization functionality that allows them to be transacted over HTTP. The small collection
//! of data types in this library are consumed by both the server and the client.

/// Represents a single attestation challenge (nonce).
///
/// Challenges are formed in response to a key access request. The purpose of the key broker is to provide
/// keys (secret strings) in exchange for a verifiable attestation token. In order to produce an attestation
/// token, the client must first be given the challenge value (commonly called a "nonce"). This structure
/// provides the nonce along with a vector of permissible evidence content types that the server will
/// accept.
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct AttestationChallenge {
/// Base64 encoding of the challenge value (nonce). The client must incorporate this challenge value
/// into its attestation token/evidence, according to the conventions of the evidence bundle being
/// formed.
pub challenge: String,

/// List of acceptable evidence media types, such as "application/eat-collection; profile=http://arm.com/CCA-SSD/1.0.0".
pub accept: Vec<EvidenceContentType>,
}

/// A request to access a key or secret string according to the "background check" interaction pattern
/// for attestation.
///
/// The identity of the key being accessed is not part of this structure, because it is implicit in the path
/// of the API request to access a key.
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct BackgroundCheckKeyRequest {
/// The public part of the wrapping key pair that the client has specified for encryption of the key/secret
/// in transit. The keybroker server uses this public key to wrap (encrypt) the data before returning
/// it to the client. This is in order for confidentiality to be maintained without relying solely on TLS
/// between the client and the server.
pub pubkey: PublicWrappingKey,
}

/// Represents an error occurring within the API usage.
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ErrorInformation {
/// Formal type string for the error.
pub r#type: String,

/// Human-readable error details, giving more information about the error.
pub detail: String,
}

#[cfg(test)]
mod tests {
use super::*;
pub type EvidenceBytes = String;

pub type EvidenceContentType = String;

/// The public portion of a wrapping key pair used to protect keys/secrets in transit between the client and
/// the server.
///
/// Wrapping keys are used so that confidentiality of the brokered data is maintained without relying solely
/// on TLS between the client and the server.
///
/// Only the client (within its confidential compute environment) has the private part of the key pair, with
/// which it can decrypt and use the data from the server.
///
/// Only RSA keys are currently supported for wrapping.
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct PublicWrappingKey {
/// Public key type. This must be "RSA".
pub kty: String,

/// Encryption algorithm. This must be either "RSA1_5" or "OAEP".
pub alg: String,

/// Base64 encoding of the public key modulus.
pub n: String,

/// Base64 encoding of the public key exponent.
pub e: String,
}

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
/// Wrapped/encrypted secret data returned from the server in the case of a successfully-verified attestation.
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct WrappedKeyData {
/// Base64 encoding of encrypted data. The client should Base64-decode this string, and then RSA decrypt the
/// resulting vector of bytes in order to obtain the secret data payload.
pub data: String,
}
17 changes: 17 additions & 0 deletions rust-keybroker/keybroker-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@
name = "keybroker-server"
version = "0.1.0"
edition = "2021"
authors = ["Veraison Project Contributors"]
description = "A simple web service that can provide keys and secrets in exchange for verifiable attestation tokens."
license = "Apache-2.0"
repository = "https://github.com/veraison/keybroker-demo"
readme = "README.md"
keywords = ["security", "service", "attestation"]
categories = ["cryptography", "hardware-support"]

[dependencies]
actix-web = "4"
base64 = "0.22.1"
thiserror = "1.0.63"
serde = "1.0.204"
rsa = "0.9.6"
sha2 = "0.10.8"
rand = "0.8.5"
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" }
125 changes: 125 additions & 0 deletions rust-keybroker/keybroker-server/src/challenge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2024 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

//! This module handles the creation and caching of challenges.
//!
//! A challenge is allocated whenever a client of the keybroker makes its initial request to access a key from the store.
//! Keys are never returned directly to the client. Instead, a challenge is created. This challenge invites the client to
//! form a bundle of attestation evidence to prove that it is trustworthy to receive the key. The client must incorporate
//! the challenge value (commonly called a "nonce") into the signed attestation evidence, according to the specific
//! conventions of the evidence type that it employs.
//!
//! When a challenge is allocated, it is cached by the server along with all of the details that the client initially
//! provided: included the identity of the key that it wants to access, and the public wrapping key that it provided in
//! order to keep the data protected in transit.
//!
//! Challenges are given their own unique identities (simple 32-bit integer values) and cached within the keybroker service,
//! with the expectation that the client will later attempt to redeem the challenge by submitting an evidence bundle.
//!
use crate::error::Result;
use keybroker_common::PublicWrappingKey;
use std::collections::HashMap;

/// Represents a single challenge, and provides the challenge value ("nonce") while also remembering the information
/// that the client provided in order to access a key.
#[derive(Debug, Clone)]
pub struct Challenge {
/// The identity of the challenge itself. A simple integer value, designed to be unique only within the keybroker
/// server instance. This value (represented in decimal) also forms part of the URL path when the client
/// redeems the challenge by supplying the attestation evidence bundle.
pub challenge_id: u32,

/// The identity of the key that the client wants to access.
pub key_id: String,

/// The public part of the wrapping key pair that the client has specified for use in order to protect the
/// secret data in transit when it is later returned.
pub wrapping_key: PublicWrappingKey,

/// The challenge value (nonce) that the client must incorporate into the evidence bundle.
pub challenge_value: Vec<u8>,
}

/// This structure provides a hash map of challenges, keyed on the integer challenge identifier.
pub struct Challenger {
challenge_table: HashMap<u32, Challenge>,
}

// This is the challenge value from from https://git.trustedfirmware.org/TF-M/tf-m-tools/+/refs/heads/main/iat-verifier/tests/data/cca_example_token.cbor
// TODO: This is only being used during early development of the service, where the client is being mocked and therefore certain fixed values are
// expected. As soon as we move to proper random nonces and testing with real clients, this constant should be deleted.
const CCA_EXAMPLE_TOKEN_NONCE: &[u8] = &[
0x6e, 0x86, 0xd6, 0xd9, 0x7c, 0xc7, 0x13, 0xbc, 0x6d, 0xd4, 0x3d, 0xbc, 0xe4, 0x91, 0xa6, 0xb4,
0x03, 0x11, 0xc0, 0x27, 0xa8, 0xbf, 0x85, 0xa3, 0x9d, 0xa6, 0x3e, 0x9c, 0xe4, 0x4c, 0x13, 0x2a,
0x8a, 0x11, 0x9d, 0x29, 0x6f, 0xae, 0x6a, 0x69, 0x99, 0xe9, 0xbf, 0x3e, 0x44, 0x71, 0xb0, 0xce,
0x01, 0x24, 0x5d, 0x88, 0x94, 0x24, 0xc3, 0x1e, 0x89, 0x79, 0x3b, 0x3b, 0x1d, 0x6b, 0x15, 0x04,
];

impl Challenger {
pub fn new() -> Challenger {
Challenger {
challenge_table: HashMap::new(),
}
}

/// Allocate a new challenge and store it in the table.
///
/// The inputs are the identity of the key that the client wants to access, and the public wrapping
/// key that the client has specified to encrypt and protect the data in transit.
pub fn create_challenge(
&mut self,
key_id: &str,
wrapping_key: &PublicWrappingKey,
) -> Challenge {
// All challenges are given random u32 identities
let mut challenge_id: u32 = rand::random();

// Simple lightweight collision avoidance - probably not needed given u32 distribution space,
// but it's easy to do. Also allows us to throw out zero if we get it.
while challenge_id == 0 || self.challenge_table.contains_key(&challenge_id) {
challenge_id = rand::random();
}

let challenge = Challenge {
challenge_id,
key_id: key_id.to_owned(),
wrapping_key: wrapping_key.clone(),
// TODO: We should create a random nonce here. The use of this mock value allows the
// server to be tested with a hard-coded example attestation token during development.
challenge_value: CCA_EXAMPLE_TOKEN_NONCE.to_vec(),
};

self.challenge_table.insert(challenge_id, challenge.clone());

challenge
}

/// Looks up a challenge in the table and returns it, failing if no such challenge is found.
pub fn get_challenge(&self, challenge_id: u32) -> Result<Challenge> {
let challenge = self.challenge_table.get(&challenge_id);
match challenge {
Some(c) => Ok(c.clone()),
None => Err(crate::error::Error::Challenge(
crate::error::ChallengeErrorKind::ChallengeNotFound,
)),
}
}

/// Deletes a challenge from the table, failing if no such challenge is found.
///
/// The key broker deletes challenges eagerly, rather than relying on a garbage collection mechanism.
/// This is for the sake of simplicity, since this is only a demo keybroker. Challenges are deleted
/// as soon as the client makes an attempt to redeem the challenge by providing an attestation
/// token. This happens even if the attestation verification fails, meaning that the client only
/// has one opportunity to redeem any given challenge, otherwise it needs to begin the key
/// request all over again.
pub fn delete_challenge(&mut self, challenge_id: u32) -> Result<()> {
let challenge = self.challenge_table.remove(&challenge_id);
match challenge {
Some(_c) => Ok(()),
None => Err(crate::error::Error::Challenge(
crate::error::ChallengeErrorKind::ChallengeNotFound,
)),
}
}
}
74 changes: 74 additions & 0 deletions rust-keybroker/keybroker-server/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2024 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

use thiserror::Error;

/// Top-level error type for the whole of the key broker service.
#[derive(Error, Debug)]
pub enum Error {
/// Represents errors resulting from the Veraison API usage (when the keybroker calls out to Veraison to verify attestation tokens).
#[error(transparent)]
VeraisonApi(#[from] veraison_apiclient::Error),

/// Represents errors from the use of the attestation results library. These errors may occur when inspecting attestation
/// results in order to implement an appraisal policy.
#[error(transparent)]
Ear(#[from] ear::Error),

/// Represents errors in verification that are not API usage errors.
#[error(transparent)]
Verification(#[from] VerificationErrorKind),

/// Represents errors related to RSA encryption or decryption. These can occur when using the RSA algorithm to wrap
/// key data from the server.
#[error(transparent)]
Rsa(#[from] rsa::Error),

/// Represents an error from the key store, such as an attempt to access a key that does not exist.
#[error(transparent)]
KeyStore(#[from] KeyStoreErrorKind),

/// Represents an error from the challenge manager, such as an attempt to lookup a challenge that was
/// never allocated.
#[error(transparent)]
Challenge(#[from] ChallengeErrorKind),

/// Represents errors related to base64 decoding, which can occur when processing the various base64 strings
/// that are transacted through the API between the client and the server, if the client provides faulty data.
#[error(transparent)]
Base64Decode(#[from] base64::DecodeError),
}

/// Errors happening within the verification process logic.
#[derive(Error, Debug)]
pub enum VerificationErrorKind {
/// It was not possible to find the challenge-response newSession endpoint
#[error("No newChallengeResponseSession endpoint was found on the Veraison server.")]
NoChallengeResponseEndpoint,
}

/// Errors happening within the key store.
#[derive(Error, Debug)]
pub enum KeyStoreErrorKind {
/// Attempt to obtain a key that is not in the store.
#[error("Requested key is not in the store.")]
KeyNotFound,

/// The client provided a wrapping key that is not an RSA key.
#[error("The wrapping key type is not supported. Wrapping key must be an RSA key.")]
UnsupportedWrappingKeyType,

/// The client provided a wrapping key whose algorithm was not supported.
#[error("Thw wrapping key encryption algorithm is not supported.")]
UnsupportedWrappingKeyAlgorithm,
}

/// Errors related to the management of challenges
#[derive(Error, Debug)]
pub enum ChallengeErrorKind {
/// Attempt to lookup a challenge with an unknown ID.
#[error("Reference to a challenge that does not exist.")]
ChallengeNotFound,
}

pub type Result<T> = std::result::Result<T, Error>;
Loading

0 comments on commit d3a6950

Please sign in to comment.