Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 57 additions & 0 deletions experimental/uefi/app/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions experimental/uefi/loader/src/qemu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason for Broadwell-IBRS? Granted, I don't know what the default CPU that qemu emulates is, but if you want something with RDRAND I think you can explicitly ask for that with +rdrand, something like "-cpu host,+rdrand". I think explicitly listing that we require rdrand is better than hiding it in the CPU definition.

That being said, qemu docs are not the best, so please do check the syntax for the -cpu flag to see how it works.

Copy link
Author

@jul-sh jul-sh Apr 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason for Broadwell-IBRS?

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");
Expand Down
2 changes: 2 additions & 0 deletions experimental/uefi/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ license = "Apache-2.0"
[dependencies]
ciborium = { version = "*", default-features = false }
ciborium-io = "*"
oak_remote_attestation_sessions = { path = "../../../remote_attestation_sessions" }
anyhow = { version = "*", default-features = false }
19 changes: 14 additions & 5 deletions experimental/uefi/runtime/src/echo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@

extern crate alloc;

use crate::Channel;
use crate::{remote_attestation::AttestationHandler, Channel};
use alloc::vec::Vec;
use ciborium::{de, ser};

#[derive(Debug)]
pub enum Error<T> {
// An error occured while deserializing.
De(ciborium::de::Error<T>),
Deserialization(ciborium::de::Error<T>),

// An error occured while serializing.
Ser(ciborium::ser::Error<T>),
Serialization(ciborium::ser::Error<T>),

// An error occured in remote attestation.
Attestation(anyhow::Error),
}

impl<E> ciborium_io::Write for &mut dyn Channel<Error = E> {
Expand All @@ -49,10 +52,16 @@ impl<E> ciborium_io::Read for &mut dyn Channel<Error = E> {
}
}

const MOCK_SESSION_ID: [u8; 8] = [0; 8];

// Echoes all input on the interface back out.
pub fn echo<E: core::fmt::Debug>(mut channel: &mut dyn Channel<Error = E>) -> Result<!, Error<E>> {
let attestation_handler = &mut AttestationHandler::create(|v| v);
loop {
let msg: Vec<u8> = de::from_reader(&mut channel).map_err(Error::De)?;
ser::into_writer(&msg, &mut channel).map_err(Error::Ser)?;
let msg: Vec<u8> = de::from_reader(&mut channel).map_err(Error::Deserialization)?;
let response = attestation_handler
.message(MOCK_SESSION_ID, msg)
.map_err(Error::Attestation)?;
ser::into_writer(&response, &mut channel).map_err(Error::Serialization)?;
}
}
1 change: 1 addition & 0 deletions experimental/uefi/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#![feature(never_type)]

pub mod echo;
mod remote_attestation;

/// Basic hardware abstraction layer for sending data.
pub trait Channel {
Expand Down
94 changes: 94 additions & 0 deletions experimental/uefi/runtime/src/remote_attestation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// 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.
extern crate alloc;

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>
where
F: Send + Sync + Clone + FnOnce(Vec<u8>) -> Vec<u8>,
{
pub fn create(request_handler: F) -> Self {
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)
}
}