-
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?
Conversation
/// 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, will factor it out into a separate trait.
/// 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 comment
The 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 comment
The 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.
type Message = Vec<u8>; | ||
|
||
/// Represents a set of claims for the trusted host and application, provides capability | ||
/// sign and verify application data. |
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?
/// 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, platform would be a better name here, will rename it.
/// | ||
/// # Argumnets | ||
/// | ||
/// * `messages` - A set of messsages to send through the channel. |
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.
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.
Also I am hoping we could expose a raw micro RPC transport to talk to the host, and the application will use that in whichever way it wants; would that be enough for your use case?
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.
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.
/// 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 comment
The 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 comment
The 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.
/// 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 comment
The 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 comment
The 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:
- the access to the host (platform) will have to be exposed separately (say, through a separate init method on the application)
- the clock reading will have to be either exposed separately through the host (platform) or encapsulated into the messages themselves.
I feel like these two consequences will make the trait less clear and more complex.
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.
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.
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.
Please take a look a the comment replies.
/// # 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 comment
The reason will be displayed to describe this comment to others. Learn more.
I think SignatureVerifier
can be a separate entity, which will take a public key taken from the Evidence
and certificate
containing the signed data, and verify the certificate.
And AttestationVerifier
will take an Evidence
and reference values (or a policy) and verify it.
|
||
/// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
I think it could have a method for getting Evidence
, but the Attestation
trait shouldn't itself represent an evidence, since Attestation
will represent an API to the Restricted Kernel.
It also could be renamed to Attester
to comply with the RATS standard.
/// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
As per previous comment, AttestationVerifier
could be a separate entity, since it doesn't need anything from the Restricted Kernel. You just provide it with a policy (which itself could be generated from self evidence, i.e. policy = is a given evidence equal to self evidence) and it verifies it.
No description provided.