-
Notifications
You must be signed in to change notification settings - Fork 128
Implement simple remote attestation for the UEFI app #2742
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
Changes from 7 commits
f7894b0
211d1ef
3a39d44
a314564
03ca895
ef7cd63
cc8c239
7d4778f
03c977a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,9 +28,11 @@ | |
| extern crate log; | ||
| extern crate alloc; | ||
|
|
||
| use crate::remote_attestation::AttestationHandler; | ||
| use ciborium::{de, ser}; | ||
| use uefi::{prelude::*, table::runtime::ResetType}; | ||
|
|
||
| mod remote_attestation; | ||
| mod serial; | ||
|
|
||
| // The main entry point of the UEFI application. | ||
|
|
@@ -71,6 +73,8 @@ fn main(handle: Handle, system_table: &mut SystemTable<Boot>) -> Status { | |
| serial_echo(handle, system_table.boot_services(), ECHO_SERIAL_PORT_INDEX).unwrap(); | ||
| } | ||
|
|
||
| const MOCK_SESSION_ID: [u8; 8] = [0; 8]; | ||
|
|
||
| // Opens the index-th serial port on the system and echoes back all frames sent over the serial | ||
| // port. | ||
| // | ||
|
|
@@ -85,6 +89,7 @@ fn main(handle: Handle, system_table: &mut SystemTable<Boot>) -> Status { | |
| // Normally does not return, unless an error is raised. | ||
| fn serial_echo(handle: Handle, bt: &BootServices, index: usize) -> Result<!, uefi::Error<()>> { | ||
|
||
| let mut serial = serial::Serial::get(handle, bt, index)?; | ||
| let attestation_handler = &mut AttestationHandler::create(|v| v); | ||
| loop { | ||
| let msg: alloc::vec::Vec<u8> = de::from_reader(&mut serial).map_err(|err| match err { | ||
| de::Error::Io(err) => err, | ||
|
|
@@ -98,7 +103,13 @@ fn serial_echo(handle: Handle, bt: &BootServices, index: usize) -> Result<!, uef | |
| } | ||
| de::Error::RecursionLimitExceeded => uefi::Error::from(Status::ABORTED), | ||
| })?; | ||
| ser::into_writer(&msg, &mut serial).map_err(|err| match err { | ||
| let response = attestation_handler | ||
| .message(MOCK_SESSION_ID, msg) | ||
| .map_err(|err| { | ||
| error!("Error handling remote attestation: {:?}", err); | ||
| uefi::Error::from(Status::PROTOCOL_ERROR) | ||
| })?; | ||
| ser::into_writer(&response, &mut serial).map_err(|err| match err { | ||
| ser::Error::Io(err) => err, | ||
| ser::Error::Value(msg) => { | ||
| error!("Error serializing value: {}", msg); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| // | ||
| // Copyright 2022 The Project Oak Authors | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
| // | ||
|
|
||
| //! Server-side implementation of the bidirectional gRPC remote attestation handshake | ||
| //! protocol. | ||
| //! | ||
| //! A simplified version of the implementation from the `grpc_unary_attestation` | ||
| //! crate. TODO(#2741): Refactor this to share more code between the two runtimes. | ||
|
||
| use alloc::vec::Vec; | ||
| use anyhow::Context; | ||
| use oak_remote_attestation_sessions::{SessionId, SessionState, SessionTracker}; | ||
|
|
||
| /// Number of sessions that will be kept in memory. | ||
| const SESSIONS_CACHE_SIZE: usize = 10000; | ||
|
|
||
| pub struct AttestationHandler<F> { | ||
| session_tracker: SessionTracker, | ||
| request_handler: F, | ||
| } | ||
|
|
||
| const MOCK_TEE_CERTIFICATE: [u8; 0] = []; | ||
| const MOCK_ADDITIONAL_INFO: [u8; 0] = []; | ||
|
|
||
| impl<F> AttestationHandler<F> | ||
|
Collaborator
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 feels like this is more generic functionality, and could be reused for different scenarios. It might be a good idea to move it somewhere more reusable (either
Collaborator
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 you mentioned in #2742 (comment) this can be done later as part of #2471 |
||
| where | ||
| F: Send + Sync + Clone + FnOnce(Vec<u8>) -> Vec<u8>, | ||
| { | ||
| pub fn create(request_handler: F) -> Self { | ||
|
Collaborator
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. isn't the normal Rust convention to call these constructor methods
Author
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. Seems like there is a loose convention towards In our codebase we tend to use both |
||
| let session_tracker = SessionTracker::create( | ||
| SESSIONS_CACHE_SIZE, | ||
| MOCK_TEE_CERTIFICATE.to_vec(), | ||
| MOCK_ADDITIONAL_INFO.to_vec(), | ||
| ); | ||
|
|
||
| Self { | ||
| session_tracker, | ||
| request_handler, | ||
| } | ||
| } | ||
|
|
||
| pub fn message(&mut self, session_id: SessionId, request: Vec<u8>) -> anyhow::Result<Vec<u8>> { | ||
| let mut session_state = { | ||
| self.session_tracker | ||
| .pop_or_create_session_state(session_id) | ||
| .expect("Couldn't pop session state") | ||
| }; | ||
| let response_body = match session_state { | ||
| SessionState::HandshakeInProgress(ref mut handshaker) => { | ||
| handshaker | ||
| .next_step(&request) | ||
| .context("Couldn't process handshake message")? | ||
| // After receiving a valid `ClientIdentity` message | ||
| // (the last step of the key exchange) | ||
| // ServerHandshaker.next_step returns `None`. For unary | ||
| // request we do want to send an explicit confirmation in | ||
| // the form of a status message. Hence in case of `None` | ||
| // fallback to a default (empty) response. | ||
| .unwrap_or_default() | ||
| } | ||
| SessionState::EncryptedMessageExchange(ref mut encryptor) => { | ||
| let decrypted_request = encryptor | ||
| .decrypt(&request) | ||
| .context("Couldn't decrypt response")?; | ||
|
|
||
| let response = (self.request_handler.clone())(decrypted_request); | ||
|
|
||
| encryptor | ||
| .encrypt(&response) | ||
| .context("Couldn't encrypt response")? | ||
| } | ||
| }; | ||
|
|
||
| self.session_tracker | ||
| .put_session_state(session_id, session_state); | ||
|
|
||
| Ok(response_body) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -71,6 +71,10 @@ impl Qemu { | |
| // Construct the command-line arguments for `qemu`. See | ||
| // https://www.qemu.org/docs/master/system/invocation.html for all available options. | ||
|
|
||
| // Needed to expose advanced CPU features to the UEFI app. Specifically | ||
| // RDRAND which is required for remote attestation. | ||
| cmd.arg("-enable-kvm"); | ||
| cmd.args(&["-cpu", "Broadwell-IBRS"]); | ||
|
Collaborator
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. Any particular reason for That being said, qemu docs are not the best, so please do check the syntax for the
Author
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.
No strong reason for the particular model, other than it supports RDRAND while being old enough that it can hopefully be virtualized by most hosts. This particular line just matches what we went with in #2703. In general we do want to specify a specific model. The reason is that in virtualization mode, qemu does not use a default CPU. Instead it's either host passthrough (unstable between hosts, not recommended by docs) or a named model. :) See https://qemu-project.gitlab.io/qemu/system/qemu-cpu-models.html#two-ways-to-configure-cpu-models-with-qemu-kvm for more details |
||
| // We're going to run qemu as a noninteractive embedded program, so disable any | ||
| // graphical outputs. | ||
| cmd.arg("-nographic"); | ||
|
|
||
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.
Mock for now as we only have a single proof of concept client. Will swap this out once I amend the client code to support attestation