Skip to content

Commit

Permalink
Incorporate Oak Standalone into the SDK, add demo
Browse files Browse the repository at this point in the history
This CR does the following:
* Moves Oak Standalone functionality out of a separate crate, and into a
  module inside the `oak_containers_sdk`.
* Updates the Oak Standalone helpers to conform to the new "thinhost"
  approach to TEE app creation. With this change, a separate service is
  no longer needed, all crypto and attestation functionality are handled
  directly by the TEE app.
* Updates the hello world trusted app to include an integration test
  demonstrating the standalone configuration.

Fixed: b/356954630
Fixed: b/356955062

Change-Id: I1c3e0c331e7f75caabc2c5a3b83640b8ed72595a
  • Loading branch information
jblebrun committed Aug 8, 2024
1 parent dfed386 commit c38ebfc
Show file tree
Hide file tree
Showing 16 changed files with 264 additions and 457 deletions.
1 change: 1 addition & 0 deletions oak_client/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ rust_library(
srcs = [
"src/client.rs",
"src/lib.rs",
"src/standalone.rs",
"src/transport.rs",
"src/verifier.rs",
],
Expand Down
1 change: 1 addition & 0 deletions oak_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
//

pub mod client;
pub mod standalone;
pub mod transport;
pub mod verifier;
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,47 @@
// 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.
//

//! Utilities for creating an [`OakClient`] instance that can interact with Oak
//! Standalone server instances.
//!
//! The Oak Standalone SDK allows easy development iteration and testing for an
//! Oak-enabled trusted binary without requiring the entire Oak containers
//! stack.
//!
//! Oak Standalone supports setting up an encrypted channel based on randomly
//! generated public keys.
//!
//! It does not currently support any sort of attestation flow.

use anyhow::Context;
use oak_client::{
client::OakClient,
transport::{EvidenceProvider, Transport},
verifier::AttestationVerifier,
};
use oak_proto_rust::oak::attestation::v1::{
extracted_evidence, Endorsements, Evidence, ExtractedEvidence, OakStandaloneData,
};
/// An [AttestationVerifier] that skips attestation, but still receives the
/// public key from the server to set up an encryption channel.
struct StandaloneAttestationVerifier {}

impl AttestationVerifier for StandaloneAttestationVerifier {
use crate::verifier::AttestationVerifier;

/// An [`AttestationVerifier`] that performs no attestation verification, but
/// still receives the public key from the server to set up an encryption
/// channel.
///
/// This verifier should only be used during development and testing using the
/// Oak Standalone SDK featureset.
///
/// As we continually developing Oak Standalone, this implementation should
/// converge towards the standard implementation, and perhaps eventually be
/// removed completely.
#[derive(Default)]
pub struct StandaloneNoOpAttestationVerifier {}

impl StandaloneNoOpAttestationVerifier {
pub fn new() -> Self {
Self::default()
}
}

impl AttestationVerifier for StandaloneNoOpAttestationVerifier {
fn verify(&self, evidence: &Evidence, _: &Endorsements) -> anyhow::Result<ExtractedEvidence> {
// Ignore everything about Evidence, Endorsements.
// For this PoC, we just pull the key directly out of ApplicationKeys,
Expand All @@ -47,13 +73,3 @@ impl AttestationVerifier for StandaloneAttestationVerifier {
})
}
}

/// Create a new [OakClient] instance that is meant to interact with an Oak
/// Standalone server facade.
///
/// Attestation will not be verified, but the server should provide an
/// encryption PK that can be used to establish a crypto channel.
pub async fn new<T: Transport + EvidenceProvider>(transport: T) -> anyhow::Result<OakClient<T>> {
let verifier = StandaloneAttestationVerifier {};
OakClient::create(transport, &verifier).await
}
18 changes: 17 additions & 1 deletion oak_containers_examples/hello_world/trusted_app/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

load("@rules_oci//oci:defs.bzl", "oci_image")
load("@rules_pkg//pkg:tar.bzl", "pkg_tar")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
load("//bazel:defs.bzl", "oci_runtime_bundle")

package(
Expand Down Expand Up @@ -48,6 +48,22 @@ rust_library(
],
)

rust_test(
name = "standalone_test",
srcs = ["tests/standalone_test.rs"],
deps = [
":lib",
"//oak_client",
"//oak_containers_examples/hello_world/proto:hello_world_rust_proto",
"//oak_containers_sdk",
"//oak_crypto",
"//oak_proto_rust/grpc",
"@oak_crates_index//:anyhow",
"@oak_crates_index//:tokio",
"@oak_crates_index//:tonic",
],
)

rust_binary(
name = "oak_containers_hello_world_trusted_app",
srcs = ["src/main.rs"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let join_handle = tokio::spawn(oak_containers_hello_world_trusted_app::app_service::create(
listener,
OakSessionContext::new(
encryption_key_handle,
Box::new(encryption_key_handle),
endorsed_evidence,
Box::new(oak_containers_hello_world_trusted_app::app::HelloWorldApplicationHandler {
application_config,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//
// Copyright 2024 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.

use std::net::{IpAddr, Ipv4Addr, SocketAddr};

use anyhow::{Context, Result};
use oak_client::{
client::OakClient, standalone::StandaloneNoOpAttestationVerifier,
transport::GrpcStreamingTransport,
};
use oak_containers_sdk::{
standalone::{
standalone_endorsed_evidence_containing_only_public_keys, StandaloneEncryptionKeyHandle,
},
OakSessionContext,
};
use oak_hello_world_proto::oak::containers::example::trusted_application_client::TrustedApplicationClient;
use tokio::net::TcpListener;
use tonic::transport::Channel;

async fn start_server() -> Result<(SocketAddr, tokio::task::JoinHandle<Result<()>>)> {
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
let listener = TcpListener::bind(addr).await?;
let addr = listener.local_addr()?;

let encryption_key_handle = StandaloneEncryptionKeyHandle::default();

let application_config = vec![1, 2, 3, 4];

let endorsed_evidence = standalone_endorsed_evidence_containing_only_public_keys(
encryption_key_handle.public_key(),
);

Ok((
addr,
tokio::spawn(oak_containers_hello_world_trusted_app::app_service::create(
listener,
OakSessionContext::new(
Box::new(encryption_key_handle),
endorsed_evidence,
Box::new(
oak_containers_hello_world_trusted_app::app::HelloWorldApplicationHandler {
application_config,
},
),
),
)),
))
}

#[tokio::test]
async fn test1() {
// Start server
let (addr, _join_handle) = start_server().await.unwrap();

let url = format!("http://{addr}");

println!("Connecting to test server on {}", url);

let channel = Channel::from_shared(url)
.context("couldn't create gRPC channel")
.unwrap()
.connect()
.await
.context("couldn't connect via gRPC channel")
.unwrap();

let mut client = TrustedApplicationClient::new(channel);

let transport = GrpcStreamingTransport::new(|rx| client.session(rx))
.await
.expect("couldn't create GRPC streaming transport");

let attestation_verifier = StandaloneNoOpAttestationVerifier::new();

// Create client
let mut oak_client = OakClient::create(transport, &attestation_verifier).await.unwrap();

// Send single request, see the response
assert_eq!(
oak_client.invoke(b"standalone user").await.unwrap(),
b"Hello from the trusted side, standalone user! Btw, the Trusted App has a config with a length of 4 bytes."
);
}
1 change: 1 addition & 0 deletions oak_containers_sdk/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ rust_library(
"src/lib.rs",
"src/oak_session_context.rs",
"src/orchestrator_client.rs",
"src/standalone.rs",
"src/tonic.rs",
],
proc_macro_deps = [
Expand Down
1 change: 1 addition & 0 deletions oak_containers_sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
pub mod crypto;
pub mod oak_session_context;
pub mod orchestrator_client;
pub mod standalone;
pub mod tonic;

// Unix Domain Sockets do not use URIs, hence this URI will never be used.
Expand Down
10 changes: 4 additions & 6 deletions oak_containers_sdk/src/oak_session_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// limitations under the License.

use anyhow::Context;
use oak_crypto::encryptor::ServerEncryptor;
use oak_crypto::{encryption_key::AsyncEncryptionKeyHandle, encryptor::ServerEncryptor};
use oak_proto_rust::oak::{
crypto::v1::{EncryptedRequest, EncryptedResponse},
session::v1::{
Expand All @@ -25,8 +25,6 @@ use oak_proto_rust::oak::{

const EMPTY_ASSOCIATED_DATA: &[u8] = b"";

use crate::InstanceEncryptionKeyHandle;

/// An Oak trusted application will write an implementation of
/// `ApplicationHandler` that accepts a serialized request (including di)
#[async_trait::async_trait]
Expand All @@ -40,14 +38,14 @@ pub trait ApplicationHandler: Send + Sync {
/// the instance of the class that actually implements the application
/// logic.
pub struct OakSessionContext {
encryption_key_handle: InstanceEncryptionKeyHandle,
encryption_key_handle: Box<dyn AsyncEncryptionKeyHandle + Send + Sync>,
endorsed_evidence: EndorsedEvidence,
application_handler: Box<dyn ApplicationHandler>,
}

impl OakSessionContext {
pub fn new(
encryption_key_handle: InstanceEncryptionKeyHandle,
encryption_key_handle: Box<dyn AsyncEncryptionKeyHandle + Send + Sync>,
endorsed_evidence: EndorsedEvidence,
application_handler: Box<dyn ApplicationHandler>,
) -> Self {
Expand All @@ -69,7 +67,7 @@ impl OakSessionContext {

// Associated data is ignored.
let (server_encryptor, name_bytes, _) =
ServerEncryptor::decrypt_async(encrypted_request, &self.encryption_key_handle)
ServerEncryptor::decrypt_async(encrypted_request, &*self.encryption_key_handle)
.await
.context("couldn't decrypt request")?;

Expand Down
107 changes: 107 additions & 0 deletions oak_containers_sdk/src/standalone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// Copyright 2024 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.

//! The Oak Standalone SDK allows easy development iteration and testing for an
//! Oak-enabled trusted binary without requiring the entire Oak containers
//! stack.
//!
//! Oak Standalone supports setting up an encrypted channel based on randomly
//! generated public keys.
//!
//! It does not currently support any sort of attestation flow.

use oak_crypto::{
encryption_key::{generate_encryption_key_pair, AsyncEncryptionKeyHandle, EncryptionKey},
hpke::RecipientContext,
};
use oak_proto_rust::oak::{
attestation::v1::{
endorsements, ApplicationKeys, Endorsements, Evidence, OakStandaloneEndorsements,
RootLayerEvidence, TeePlatform,
},
session::v1::EndorsedEvidence,
};

/// Create an [`EndorsedEvidence`] instance that your TEE application can use.
/// This can be provided in circumstances where you'd normally request an
/// [`EndorsedEvidence`] from the Orchestrator.
pub fn standalone_endorsed_evidence_containing_only_public_keys(
public_key: impl Into<Vec<u8>>,
) -> EndorsedEvidence {
EndorsedEvidence {
evidence: Some(Evidence {
// TODO: b/347970899 - Create something here that will be compatible with the
// verification framework.
root_layer: Some(RootLayerEvidence {
platform: TeePlatform::None.into(),
eca_public_key: vec![],
remote_attestation_report: vec![],
}),
layers: vec![],
application_keys: Some(ApplicationKeys {
// TODO: b/347970899 - Store the public key in the format expected by
// the attestation verification framework.
encryption_public_key_certificate: public_key.into(),
group_encryption_public_key_certificate: vec![],
signing_public_key_certificate: vec![],
group_signing_public_key_certificate: vec![],
}),
}),
endorsements: Some(Endorsements {
r#type: Some(endorsements::Type::Standalone(OakStandaloneEndorsements {})),
}),
}
}

/// A structure implementing [`AsyncEncryptionKeyHandle``] trait, which can be
/// provided to a TEE application instead of the normal orchestrator-driven
/// instance type.
pub struct StandaloneEncryptionKeyHandle {
public_key: Vec<u8>,
private_key: EncryptionKey,
}

impl StandaloneEncryptionKeyHandle {
/// Generates a new public/private keypair and returns a new instance
/// containing them.
pub fn new(
private_key: EncryptionKey,
public_key: impl Into<Vec<u8>>,
) -> StandaloneEncryptionKeyHandle {
Self { private_key, public_key: public_key.into() }
}

/// Return the public_key created on construction.
pub fn public_key(&self) -> &[u8] {
&self.public_key
}
}

impl Default for StandaloneEncryptionKeyHandle {
fn default() -> Self {
let (private_key, public_key) = generate_encryption_key_pair();
Self::new(private_key, public_key)
}
}

#[async_trait::async_trait]
impl AsyncEncryptionKeyHandle for StandaloneEncryptionKeyHandle {
async fn generate_recipient_context(
&self,
encapsulated_public_key: &[u8],
) -> anyhow::Result<RecipientContext> {
self.private_key.generate_recipient_context(encapsulated_public_key).await
}
}
Loading

0 comments on commit c38ebfc

Please sign in to comment.