-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from paulhowardarm/phServerSkeleton
First functional chunk of server code
- Loading branch information
Showing
10 changed files
with
753 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
)), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; |
Oops, something went wrong.