diff --git a/Cargo.toml b/Cargo.toml index 312fce2..2f3895f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,18 @@ ring = "0.17" # hash256 rcgen = "0.13" log = "0.4" hex = "0.4" +tokio-rustls = "0.26" +tower = { version = "0.5", features = ["full"] } +tower-http = { version = "0.5", features = ["full"] } +hyper = "1.4.1" +hyper-util = "0.1.7" +hyper-rustls = { version = "0.27", features = ["http2"] } +prost = "0.13" + +[dependencies.tonic] +version = "0.12" +#features = ["rustls-0_23"] +optional = true [dependencies.actix-web] version = "4.3" @@ -52,17 +64,29 @@ version = "1" default-features = false features = ["precommit-hook", "run-cargo-test", "run-cargo-clippy"] +[build-dependencies] +tonic-build = "0.12" + [features] default = [] occlum = [] reqwest = ["dep:reqwest"] actix-web = ["dep:actix-web", "actix-service", "actix-http"] +tonic = ["dep:tonic"] [[example]] -name = "server" +name = "mratls_https_server" required-features = ["actix-web"] [[example]] -name = "client" +name = "mratls_https_client" required-features = ["reqwest"] + +[[example]] +name = "mratls_grpcs_server" +required-features = ["tonic"] + +[[example]] +name = "mratls_grpcs_client" +required-features = ["tonic"] diff --git a/README.md b/README.md index cc49c51..517bdc4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Occlum SGX Remote Attestation integrated in TLS connection Steps to test the project: + ``` occlum-cargo build --example server --features="occlum,actix-web" strip -s target/x86_64-unknown-linux-musl/debug/examples/server @@ -10,3 +11,23 @@ occlum-cargo build --example client --features="occlum,reqwest" strip -s target/x86_64-unknown-linux-musl/debug/examples/client ./build_client.sh ``` + +## Mutual RATLS examples + +Examples show how to use the mRATLS (Mutual Remote Attestation TLS) in different situations: + +* The first example shows how to create mRATLS HTTPS server and client +* The second example shows how to create mRATLS GRPCs server and client + +Both the server and the client must be running inside the enclave. +So during the remote attestation peers, acquire their RA certificates. +And during the TLS handshake, they verify each other's RA certificates. +The config allows to whitelist MRENCLAVE, MRSIGNER, PRODID, SVN of the peer. + +## RATLS examples + +Example shows how to create RATLS HTTPS server and client. +The server must be running inside the enclave. +The client can be running anywhere. +The server config allows to whitelist the public ec25519 key of the client. +The client config allows to whitelist MRENCLAVE, MRSIGNER, PRODID, SVN of the server. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..520f3da --- /dev/null +++ b/build.rs @@ -0,0 +1,6 @@ +fn main() { + tonic_build::configure() + .build_server(true) + .compile(&["examples/echo.proto"], &["examples"]) + .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); +} diff --git a/examples/echo.proto b/examples/echo.proto new file mode 100644 index 0000000..20968cd --- /dev/null +++ b/examples/echo.proto @@ -0,0 +1,37 @@ +/* + * + * Copyright 2018 gRPC 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. + * + */ + +syntax = "proto3"; + +package grpc.examples.unaryecho; + +// EchoRequest is the request for echo. +message EchoRequest { + string message = 1; +} + +// EchoResponse is the response for echo. +message EchoResponse { + string message = 1; +} + +// Echo is the echo service. +service Echo { + // UnaryEcho is unary echo. + rpc UnaryEcho(EchoRequest) returns (EchoResponse) {} +} diff --git a/examples/mratls_grpcs_client.rs b/examples/mratls_grpcs_client.rs new file mode 100644 index 0000000..baf5334 --- /dev/null +++ b/examples/mratls_grpcs_client.rs @@ -0,0 +1,64 @@ +pub mod pb { + tonic::include_proto!("/grpc.examples.unaryecho"); +} + +use hyper::Uri; +use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; +use occlum_ratls::prelude::*; +use occlum_ratls::RaTlsConfigBuilder; +use occlum_sgx::SGXMeasurement; +use pb::{echo_client::EchoClient, EchoRequest}; +use tokio_rustls::rustls::ClientConfig; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mrsigner_hex = "83D719E77DEACA1470F6BAF62A4D774303C899DB69020F9C70EE1DFC08C7CE9E"; + let mut mrsigner = [0u8; 32]; + hex::decode_to_slice(mrsigner_hex, &mut mrsigner).expect("mrsigner decoding failed"); + + let config = RaTlsConfig::new().allow_instance_measurement( + InstanceMeasurement::new().with_mrsigners(vec![SGXMeasurement::new(mrsigner)]), + ); + + let tls = ClientConfig::from_ratls_config(config) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))?; + + let mut http = HttpConnector::new(); + http.enforce_http(false); + + // We have to do some wrapping here to map the request type from + // `https://example.com` -> `https://[::1]:50051` because `rustls` + // doesn't accept ip's as `ServerName`. + let connector = tower::ServiceBuilder::new() + .layer_fn(move |s| { + let tls = tls.clone(); + + hyper_rustls::HttpsConnectorBuilder::new() + .with_tls_config(tls) + .https_or_http() + .enable_http2() + .wrap_connector(s) + }) + // Since our cert is signed with `example.com` but we actually want to connect + // to a local server we will override the Uri passed from the `HttpsConnector` + // and map it to the correct `Uri` that will connect us directly to the local server. + .map_request(|_| Uri::from_static("https://[::1]:50051")) + .service(http); + + let client = hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build(connector); + + // Using `with_origin` will let the codegenerated client set the `scheme` and + // `authority` from the provided `Uri`. + let uri = Uri::from_static("https://example.com"); + let mut client = EchoClient::with_origin(client, uri); + + let request = tonic::Request::new(EchoRequest { + message: "hello".into(), + }); + + let response = client.unary_echo(request).await?; + + println!("RESPONSE={:?}", response); + + Ok(()) +} diff --git a/examples/mratls_grpcs_server.rs b/examples/mratls_grpcs_server.rs new file mode 100644 index 0000000..9ef4c52 --- /dev/null +++ b/examples/mratls_grpcs_server.rs @@ -0,0 +1,114 @@ +pub mod pb { + tonic::include_proto!("/grpc.examples.unaryecho"); +} + +use hyper::server::conn::http2::Builder; +use hyper_util::{ + rt::{TokioExecutor, TokioIo}, + service::TowerToHyperService, +}; +use pb::{EchoRequest, EchoResponse}; +use std::sync::Arc; +use tokio::net::TcpListener; +use tokio_rustls::{ + rustls::{pki_types::CertificateDer, ServerConfig}, + TlsAcceptor, +}; +use tonic::{body::boxed, service::Routes, Request, Response, Status}; +use tower::ServiceBuilder; +use tower::ServiceExt; + +use occlum_ratls::prelude::*; +use occlum_ratls::RaTlsConfigBuilder; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mrsigner_hex = "83D719E77DEACA1470F6BAF62A4D774303C899DB69020F9C70EE1DFC08C7CE9E"; + let mut mrsigner = [0u8; 32]; + hex::decode_to_slice(mrsigner_hex, &mut mrsigner).expect("mrsigner decoding failed"); + + let config = RaTlsConfig::new().allow_instance_measurement( + InstanceMeasurement::new().with_mrsigners(vec![SGXMeasurement::new(mrsigner)]), + ); + + let mut tls = ServerConfig::from_ratls_config(config) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))?; + tls.alpn_protocols = vec![b"h2".to_vec()]; + + let server = EchoServer::default(); + + let svc = Routes::new(pb::echo_server::EchoServer::new(server)).prepare(); + + let http = Builder::new(TokioExecutor::new()); + + let listener = TcpListener::bind("[::1]:50051").await?; + let tls_acceptor = TlsAcceptor::from(Arc::new(tls)); + + loop { + let (conn, addr) = match listener.accept().await { + Ok(incoming) => incoming, + Err(e) => { + eprintln!("Error accepting connection: {}", e); + continue; + } + }; + + let http = http.clone(); + let tls_acceptor = tls_acceptor.clone(); + let svc = svc.clone(); + + tokio::spawn(async move { + let mut certificates = Vec::new(); + + let conn = tls_acceptor + .accept_with(conn, |info| { + if let Some(certs) = info.peer_certificates() { + for cert in certs { + certificates.push(cert.clone()); + } + } + }) + .await + .unwrap(); + + let extension_layer = + tower_http::add_extension::AddExtensionLayer::new(Arc::new(ConnInfo { + addr, + certificates, + })); + let svc = ServiceBuilder::new().layer(extension_layer).service(svc); + + http.serve_connection( + TokioIo::new(conn), + TowerToHyperService::new(svc.map_request(|req: hyper::Request<_>| req.map(boxed))), + ) + .await + .unwrap(); + }); + } +} + +#[derive(Debug)] +struct ConnInfo { + addr: std::net::SocketAddr, + certificates: Vec>, +} + +type EchoResult = Result, Status>; + +#[derive(Default)] +pub struct EchoServer {} + +#[tonic::async_trait] +impl pb::echo_server::Echo for EchoServer { + async fn unary_echo(&self, request: Request) -> EchoResult { + let conn_info = request.extensions().get::>().unwrap(); + println!( + "Got a request from: {:?} with certs: {:?}", + conn_info.addr, conn_info.certificates + ); + + let message = request.into_inner().message; + Ok(Response::new(EchoResponse { message })) + } +} diff --git a/examples/client.rs b/examples/mratls_https_client.rs similarity index 100% rename from examples/client.rs rename to examples/mratls_https_client.rs diff --git a/examples/server.rs b/examples/mratls_https_server.rs similarity index 100% rename from examples/server.rs rename to examples/mratls_https_server.rs diff --git a/src/http/mod.rs b/src/http/mod.rs index e9551a7..1f29384 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -2,3 +2,4 @@ pub mod actix_web; #[cfg(feature = "reqwest")] pub mod reqwest; +//mod tonic_server; diff --git a/src/http/tonic_server.rs b/src/http/tonic_server.rs new file mode 100644 index 0000000..a1f0bf6 --- /dev/null +++ b/src/http/tonic_server.rs @@ -0,0 +1,97 @@ +use hyper::server::conn::http2::Builder; +use hyper_util::{ + rt::{TokioExecutor, TokioIo}, + service::TowerToHyperService, +}; +use std::sync::Arc; +use tokio::net::TcpListener; +use tokio_rustls::{ + rustls::{pki_types::CertificateDer, ServerConfig}, + TlsAcceptor, +}; +use tonic::transport::server::TcpIncoming; +use tonic::{body::boxed, service::Routes, Request, Response, Status}; +use tower::ServiceExt; +use tower_http::ServiceBuilderExt; + +use crate::{config::RaTlsConfig, RaTlsConfigBuilder}; + +fn bind_ratls(config: RaTlsConfig) -> Result<(), std::io::Error> { + let config = ServerConfig::from_ratls_config(config) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e)))?; + let tls_acceptor = TlsAcceptor::from(Arc::new(config)); + + // add tls_acceptor to the server + let incoming = TcpIncoming::new("[::1]:50051".parse().unwrap(), true, None)?; + + let svc = tower::ServiceBuilder::new() + .add_extension(Arc::new(ConnInfo { addr, certificates })) + .service(svc); + + self.bind_rustls_0_23(addr, config) +} +async fn main() -> Result<(), Box> { + let listener = TcpListener::bind("[::1]:50051").await?; + let tls_acceptor = TlsAcceptor::from(Arc::new(tls)); + + loop { + let (conn, addr) = match listener.accept().await { + Ok(incoming) => incoming, + Err(e) => { + eprintln!("Error accepting connection: {}", e); + continue; + } + }; + + let http = http.clone(); + let tls_acceptor = tls_acceptor.clone(); + let svc = svc.clone(); + + tokio::spawn(async move { + let mut certificates = Vec::new(); + + let conn = tls_acceptor + .accept_with(conn, |info| { + if let Some(certs) = info.peer_certificates() { + for cert in certs { + certificates.push(cert.clone()); + } + } + }) + .await + .unwrap(); + + http.serve_connection( + TokioIo::new(conn), + TowerToHyperService::new(svc.map_request(|req: http::Request<_>| req.map(boxed))), + ) + .await + .unwrap(); + }); + } +} + +#[derive(Debug)] +struct ConnInfo { + addr: std::net::SocketAddr, + certificates: Vec>, +} + +type EchoResult = Result, Status>; + +#[derive(Default)] +pub struct EchoServer {} + +#[tonic::async_trait] +impl pb::echo_server::Echo for EchoServer { + async fn unary_echo(&self, request: Request) -> EchoResult { + let conn_info = request.extensions().get::>().unwrap(); + println!( + "Got a request from: {:?} with certs: {:?}", + conn_info.addr, conn_info.certificates + ); + + let message = request.into_inner().message; + Ok(Response::new(EchoResponse { message })) + } +}