-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce Platform Abstraction Layer between trusted host and trusted applications. #2
base: no_std_pr
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Trusted Computations |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "trusted-pal" | ||
version = "0.1.0" | ||
authors = ["people"] | ||
|
||
[dependencies] | ||
anyhow = { version = "1", default-features = false } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Platform Abstraction Layer for the Oak Platform |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#![no_std] | ||
|
||
extern crate alloc; | ||
extern crate anyhow; | ||
|
||
pub mod pal; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
//! This module expresses a simple abstraction layer between the trusted host and | ||
//! trusted application it runs. The main goal is to abstract away details of the | ||
//! trusted hosting and support mutiple host implementations. The host implementations | ||
//! differ in their capabilities with some imposing significant restrictions but | ||
//! offering better security and privacy guarantees. Hence the abstraction layer | ||
//! focuses on the most restricted host and assumes restricted kernel. | ||
//! | ||
//! Restricted kernel limits the execution model to processing signals (or rather | ||
//! messages) received from the untrusted launcher over a communication channel. Given | ||
//! this limitation the abstraction layer assumes that the trusted application performs | ||
//! processing in response to the signal from the untrusted launcher which can be | ||
//! either a message or a clock tick. In other words the abstraction layer defines | ||
//! a poll based execution model driven by a signal generating loop in untrusted | ||
//! launcher. | ||
//! | ||
//! [Application] trait must be implemented by a concrete trusted application to | ||
//! receive signals from the trusted host originating from untrusted launcher. | ||
//! | ||
//! [Host] trait must be implemented by a concrete trusted host to expose its capabilities | ||
//! to the trusted application. | ||
|
||
use alloc::boxed::Box; | ||
use alloc::vec::Vec; | ||
use anyhow::Result; | ||
|
||
type Message = Vec<u8>; | ||
|
||
/// Represents a set of claims for the trusted host and application, provides capability | ||
/// sign and verify application data. | ||
pub trait Attestation { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it could have a method for getting It also could be renamed to |
||
/// TODO: Expose application identity, provenance and configuration information such | ||
/// that applications can assert the properties of the peer. | ||
|
||
/// Serializes attestation such that it can be transferred over the wire to a peer | ||
/// and attested by the peer. | ||
/// | ||
/// # Returns | ||
/// | ||
/// An error if failed to serialize into a byte string, a success otherwise. | ||
/// | ||
/// # Note | ||
/// | ||
/// The serialized representation must contain enough information to | ||
/// verify the attestation itself (e.g. that it has been produced by a proper root | ||
/// of trust and that it represents a trusted chain of components) and to verify | ||
/// signed application data (e.g. trusted applications sharing data with each other). | ||
/// | ||
/// For example, before two trusted application peers can start interacting they must | ||
/// establish trust. The trusted application initiating the interaction will first | ||
/// obtain its own attestation, sign the data it wants to share with the peer, | ||
/// share serialized attestation, the data and its signature with the peer. The peer | ||
/// deserializes and verifies the attestation (see `Host::verify_peer_attestation`), | ||
/// uses the attestation to verify the data signatures, performs similar handshake | ||
/// with the peer. | ||
fn serialize(&self) -> Result<Vec<u8>>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably be its own trait and included as part of this trait. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, will factor it out into a separate trait. |
||
|
||
/// Signs a byte string using private signing key. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `data` - The byte string to sign. | ||
/// | ||
/// # Returns | ||
/// | ||
/// An error if the attestation doesn't possess the signing capability (e.g. this | ||
/// instance has been transferred over the wire and meant to be used for verification) | ||
/// or if the signing failed, a success otherwise. | ||
fn sign(&self, data: &[u8]) -> Result<Vec<u8>>; | ||
|
||
/// Verifies signature of a byte string using public signing key. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand the semantics of this method. What public key does it expect to use to verify the signature? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The attestation encapsulates attestation evidence and application signing keys that are generated at the start of the application and remain unchanged through its lifetime. The signing keys in turn are also signed using the keys from the attestation evidence. When the attestation is obtained from the host it has both public and private keys and enables application data signing. When the attestation is serialized and sent over to a peer only a public signing key is serialized. The peer can then use deserialized attestation and encapsulated public signing key to verify signed application data. |
||
/// | ||
/// # Arguments | ||
/// | ||
/// * `data` - The byte string to verify signature for. | ||
/// * `signature` - The signature of the byte string. | ||
/// | ||
/// # Returns | ||
/// | ||
/// An error if the signature cannot be verifed or is invalid, a success otherwise. | ||
fn verify(&self, data: &[u8], signature: &[u8]) -> Result<()>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think And |
||
|
||
/// Gets serialized public key used for signing by the trusted application. | ||
/// The signing key is generated at the start of the trusted application and | ||
/// remains unchanged through the lifetime. | ||
/// | ||
/// # Note | ||
/// | ||
/// Public signing key can be used to derive a trusted application identity. | ||
/// For example, a trusted application that represents a node in Raft cluster | ||
/// running inside of a group trusted hosts, it is important that Raft node | ||
/// identity cannot be forged, does not require coordination to pick one and | ||
/// has low collision chance. Hash of the public signing key is an identity | ||
/// mechanism that is compliant with these requirements. | ||
fn public_signing_key(&self) -> Vec<u8>; | ||
} | ||
|
||
/// Represents a trusted host, abstracting away the details of how trusted | ||
/// application is hosted (e.g. trusted host can be restricted kernel bare | ||
/// metal based encrypted virtual machine or a linux kernel docker based | ||
/// encrypted virtual machine). | ||
pub trait Host { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this should be called Platform or something like that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, platform would be a better name here, will rename it. |
||
/// Gets attestation that represens a set of claims about the trusted application | ||
/// and its host. The attestation is generated after the trusted application and | ||
/// host are fully initialiezed, the attestation doesn't change throughout the | ||
/// lifetime. | ||
fn get_self_attestation(&self) -> Box<dyn Attestation>; | ||
|
||
/// Gets serialized configuration that stays immutable through the lifetime of | ||
/// the trusted application. | ||
fn get_self_config(&self) -> Vec<u8>; | ||
|
||
/// Sends messages through the communication channel that connects the trusted | ||
/// application to the untrusted launcher. | ||
/// | ||
/// # Argumnets | ||
/// | ||
/// * `messages` - A set of messsages to send through the channel. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it necessary to send multiple messages atomically? Would a simpler API that allows sending a single message not be sufficient? The application can always batch multiple actions in a single message if desired, so it seems unnecessary to add another level of grouping in this API. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here send messages has a subtle semantic. Technically it doesn't send messages, it buffers them until the call to the receive_messages finishes. The application generates messages to be sent during the receive_messages call and then calls send_messages to be sent after receive_messages call completes. This is mainly due to the fact that in microRPC the call can only be issued from the Untrusted Launcher side. Hence, the send_messages buffers the messages to be sent and then pack them into a response for the microRPC. With regards to the sending one or many messages at once, given the buffering semantic, it doesn't matter. However, processing by the application even of a single incoming message may result in generation of multiple messages (for example, a proposal message for the replicated log will result num replicas messages for appending entries). Given that the purpose of this interface to abstract away the details of the platform (restricted kernel works with microRPC and Docker works with gRPC) the microRPC is the detail of how host (platform) is implemented. |
||
/// | ||
/// # Returns | ||
/// | ||
/// Error if the communication channel is irrepairably broken, a success otherise. | ||
fn send_messages(&mut self, messages: &[Message]) -> Result<()>; | ||
|
||
/// Attempts to deserialize peer attestation and perform initial verification | ||
/// of the attestation. The application specific verification (e.g. ensuring | ||
/// that attestation points to a particular binary and configuration) is the | ||
/// responsibility of the trusted application. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `peer_attestation` - A serialized attestation of a peer trusted application. | ||
/// | ||
/// # Returns | ||
/// | ||
/// A deserialized and initially verified peer attestation if success, an error | ||
/// otherwise. An error may be caused by malformed serialized representation or | ||
/// by failing verification. | ||
fn verify_peer_attestation(&self, peer_attestation: &[u8]) -> Result<Box<dyn Attestation>>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The result of this is only constrained by the Attestation trait, so I'm not sure how it's expected to be used, could you elaborate? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's imagine there is a single node Raft cluster. We are trying to add another node to the cluster by sending configuration change to the leader. The leader tries to send append entries to the new node. Before doing so, we need to establish trust and secure channel. The leader obtains its attestation from the host (platform), generates its public key for the HPKE and signs it, sends both in an initiate handshake message. The follower receives the initiate handshake message, gets serialized leader attestation and asks the host (platform) to verify (check that the DICE chain is valid) and deserialize it. Now that the follower has the deserialized attestation containing public signing key it can verify signed public key of the leader for the HPKE. Afterwards the follower performs symmetric action (gets its attestation, generates HPKE response and sign it, serializes attestation and sends it and signed HPKE response to the leader). Hope this helps. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As per previous comment, |
||
} | ||
|
||
/// Represents a trusted application running inside a trusted host. The trusted | ||
/// application is considered passive and performs execution in response to | ||
/// receiving messages through the communication channel that connects the trusted | ||
/// application to the untrusted launcher. In the absence of messages to be processed | ||
/// the trusted application will receive periodically empty set of messages to allow | ||
/// trusted application to make progress based on time change. | ||
pub trait Application { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the application may also just implement the same micro RPC transport trait. It can just always return an empty message back. Would that work? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is possible to implement the transport trait, however this will lead to two consequences:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And of course, the microRPC is not necessarily the medium (in Docker as far as I understand the gRPC is the medium) which will break the abstraction. |
||
/// Receives messages to process by the trusted application. Conceptually represents | ||
/// a computation slice. A set of messages may be empty. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `host` - Trusted host that is responsible the trusted application. Provides | ||
/// access to message sending and attestation capabilities. | ||
/// * `instant` - A measurement of a monotonically nondecreasing clock provided by | ||
/// the untrusted launcher to the trusted host. The resolution of the instant is | ||
/// mesured in milliseconds. Instants are opaque that can only be compared to one | ||
/// another. In other words the absolute value must not be interpretted as wall | ||
/// clock time or time since the trusted application start. | ||
/// * `messages` - A potentially empty set of messages received from the untrusted | ||
/// launcher for the trusted application to process. | ||
/// | ||
/// # Returns | ||
/// | ||
/// Error if the trusted application encountered an unrecoverable error and must | ||
/// be terminated, a success otherwise. The application level recoverable errors | ||
/// are represented and communicated using messages. | ||
fn receive_messages( | ||
&mut self, | ||
host: &mut impl Host, | ||
instant: u64, | ||
messages: &[Message], | ||
) -> Result<()>; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are the sign and verify methods defined on the Attestation trait? I would expect to see them on the Host trait perhaps
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea was that the attestation encapsulates signing key pair (only a public signing key if the attestation have been serialized and sent over the wire) that is used to sign (if private key is present) or verify the data. Specifically the verify functionality is mainly targeting scenarios where one TEE sends its serialized attestation along with signed data to another TEE, another TEE deserializes the attestation and then verifies the signed data using the public signing key encapsulated in the attestation. Does that makes sense?