Initial implementation
This commit is contained in:
commit
c19c42ccd7
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
target
|
||||
Cargo.lock
|
||||
client_instance
|
||||
server_instance
|
71
Cargo.toml
Normal file
71
Cargo.toml
Normal file
@ -0,0 +1,71 @@
|
||||
[package]
|
||||
name = "occlum-ratls"
|
||||
version = "0.4.5"
|
||||
edition = "2021"
|
||||
authors = ["Ivan Chirkin <chirkin.ivan@gmail.com>"]
|
||||
description = "Lib for remote attestation between occlum instances"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/aggregion/occlum-ratls"
|
||||
keywords = ["occlum", "rustls", "ratls"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
x509-parser = "0.15.0"
|
||||
#rustls = { version = "0.21", features = [
|
||||
rustls = { version = "0.20.9", features = [
|
||||
"dangerous_configuration",
|
||||
"logging"
|
||||
] }
|
||||
log = "0.4.17"
|
||||
rcgen = "0.12.1"
|
||||
occlum-sgx = "^0.1.12"
|
||||
ring = "0.17.8"
|
||||
hex = "0.4"
|
||||
|
||||
[dependencies.actix-web]
|
||||
version = "4.3.1"
|
||||
features = ["rustls-0_20"]
|
||||
optional = true
|
||||
|
||||
[dependencies.actix-http]
|
||||
version = "3.3"
|
||||
features = ["http2", "ws"]
|
||||
optional = true
|
||||
|
||||
[dependencies.actix-service]
|
||||
version = "2"
|
||||
optional = true
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "=0.11.10"
|
||||
default-features = false
|
||||
features = ["__rustls"]
|
||||
optional = true
|
||||
|
||||
[dev-dependencies.env_logger]
|
||||
version = "0.10.0"
|
||||
|
||||
[dev-dependencies.tokio]
|
||||
version = "1"
|
||||
features = ["full"]
|
||||
|
||||
[dev-dependencies.cargo-husky]
|
||||
version = "1"
|
||||
default-features = false
|
||||
features = ["precommit-hook", "run-cargo-test", "run-cargo-clippy"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
occlum = []
|
||||
reqwest = ["dep:reqwest"]
|
||||
actix-web = ["dep:actix-web", "actix-service", "actix-http"]
|
||||
|
||||
|
||||
[[example]]
|
||||
name = "server"
|
||||
required-features = ["actix-web"]
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
required-features = ["reqwest"]
|
12
README.md
Normal file
12
README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# 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
|
||||
./build_server.sh
|
||||
|
||||
occlum-cargo build --example client --features="occlum,reqwest"
|
||||
strip -s target/x86_64-unknown-linux-musl/debug/examples/client
|
||||
./build_client.sh
|
||||
```
|
11
build_client.sh
Executable file
11
build_client.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# initialize occlum workspace
|
||||
rm -rf client_instance && mkdir client_instance && cd client_instance
|
||||
|
||||
occlum init && rm -rf image
|
||||
copy_bom -f ../client.yaml --root image --include-dir /opt/occlum/etc/template
|
||||
|
||||
occlum build
|
||||
occlum run /bin/client
|
11
build_server.sh
Executable file
11
build_server.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# initialize occlum workspace
|
||||
rm -rf server_instance && mkdir server_instance && cd server_instance
|
||||
|
||||
occlum init && rm -rf image
|
||||
copy_bom -f ../server.yaml --root image --include-dir /opt/occlum/etc/template
|
||||
|
||||
occlum build
|
||||
occlum run /bin/server
|
7
client.yaml
Normal file
7
client.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
includes:
|
||||
- base.yaml
|
||||
targets:
|
||||
- target: /bin
|
||||
copy:
|
||||
- files:
|
||||
- ../target/x86_64-unknown-linux-musl/debug/examples/client
|
27
examples/client.rs
Normal file
27
examples/client.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use occlum_ratls::prelude::*;
|
||||
use reqwest::ClientBuilder;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
env_logger::init_from_env(env_logger::Env::default().default_filter_or("trace"));
|
||||
|
||||
let mrsigner_hex = "83D719E77DEACA1470F6BAF62A4D774303C899DB69020F9C70EE1DFC08C7CE9E";
|
||||
let mut mrsigner = [0u8; 32];
|
||||
hex::decode_to_slice(mrsigner_hex, &mut mrsigner)?;
|
||||
|
||||
let client = ClientBuilder::new()
|
||||
.use_ratls(
|
||||
RaTlsConfig::new().allow_instance_measurement(
|
||||
InstanceMeasurement::new()
|
||||
.with_mrsigners(vec![SGXMeasurement::new(mrsigner)])
|
||||
.with_product_ids(vec![0]),
|
||||
),
|
||||
)
|
||||
.build()?;
|
||||
let res = client.get("https://127.0.0.1:8000").send().await?;
|
||||
let data = res.text().await?;
|
||||
|
||||
println!("response: {}", data);
|
||||
|
||||
Ok(())
|
||||
}
|
37
examples/server.rs
Normal file
37
examples/server.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use actix_web::{get, App, HttpServer};
|
||||
use occlum_ratls::prelude::*;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[get("/")]
|
||||
async fn index() -> String {
|
||||
format!("Hello world!")
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::default().default_filter_or("trace"));
|
||||
|
||||
let mrsigner_hex = "83D719E77DEACA1470F6BAF62A4D774303C899DB69020F9C70EE1DFC08C7CE9E";
|
||||
let mut mrsigner = [0u8; 32];
|
||||
hex::decode_to_slice(mrsigner_hex, &mut mrsigner).expect("mrsigner decoding failed");
|
||||
|
||||
HttpServer::new(|| App::new().service(index))
|
||||
.bind_ratls(
|
||||
SocketAddr::from(([127, 0, 0, 1], 8000)),
|
||||
RaTlsConfig::new()
|
||||
.allow_instance_measurement(
|
||||
InstanceMeasurement::new().with_mrsigners(vec![SGXMeasurement::new(mrsigner)]),
|
||||
)
|
||||
.allow_instance_measurement(
|
||||
InstanceMeasurement::new()
|
||||
.with_mrenclaves(vec![
|
||||
SGXMeasurement::new([0u8; 32]),
|
||||
SGXMeasurement::new([1u8; 32]),
|
||||
])
|
||||
.with_product_ids(vec![0, 2]),
|
||||
),
|
||||
)
|
||||
.unwrap()
|
||||
.run()
|
||||
.await
|
||||
}
|
7
server.yaml
Normal file
7
server.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
includes:
|
||||
- base.yaml
|
||||
targets:
|
||||
- target: /bin
|
||||
copy:
|
||||
- files:
|
||||
- ../target/x86_64-unknown-linux-musl/debug/examples/server
|
157
src/cert.rs
Normal file
157
src/cert.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use std::error::Error;
|
||||
|
||||
#[cfg(feature = "occlum")]
|
||||
use crate::utils::hash_sha512;
|
||||
use crate::{error::RaTlsError, RaTlsConfig};
|
||||
use log::error;
|
||||
|
||||
#[cfg(feature = "occlum")]
|
||||
use occlum_sgx::SGXQuote;
|
||||
use rustls::{
|
||||
sign::{any_supported_type, CertifiedKey},
|
||||
Certificate, PrivateKey,
|
||||
};
|
||||
|
||||
use rcgen::{
|
||||
Certificate as GenCertificate, CertificateParams, CustomExtension, DistinguishedName, KeyPair,
|
||||
};
|
||||
|
||||
#[cfg(feature = "occlum")]
|
||||
use x509_parser::{nom::Parser, oid_registry::Oid, prelude::X509CertificateParser, public_key};
|
||||
|
||||
pub trait CertificateBuilder: Send + Sync {
|
||||
fn build(&self) -> Result<CertifiedKey, RaTlsError>;
|
||||
}
|
||||
|
||||
struct RaTlsCertifiedKey {
|
||||
cert_der: Vec<u8>,
|
||||
key_der: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct RaTlsCertificateBuilder {
|
||||
common_name: String,
|
||||
}
|
||||
|
||||
impl Default for RaTlsCertificateBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
common_name: "RATLS".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const REPORT_OID: [u64; 5] = [1, 2, 840, 113741, 1];
|
||||
|
||||
impl RaTlsCertificateBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_common_name(self, cn: String) -> Self {
|
||||
Self { common_name: cn }
|
||||
}
|
||||
|
||||
fn build_internal(&self) -> Result<RaTlsCertifiedKey, Box<dyn Error>> {
|
||||
let mut distinguished_name = DistinguishedName::new();
|
||||
|
||||
distinguished_name.push(rcgen::DnType::CommonName, self.common_name.clone());
|
||||
distinguished_name.push(rcgen::DnType::CountryName, "US");
|
||||
distinguished_name.push(rcgen::DnType::OrganizationName, "Aggregion");
|
||||
|
||||
let mut params = CertificateParams::default();
|
||||
let key_pair = KeyPair::generate(params.alg)?;
|
||||
|
||||
let quote = self.get_quote(&key_pair)?;
|
||||
|
||||
params.key_pair = Some(key_pair);
|
||||
params.distinguished_name = distinguished_name;
|
||||
|
||||
params.custom_extensions = vec![CustomExtension::from_oid_content(&REPORT_OID, quote)];
|
||||
|
||||
let crt = GenCertificate::from_params(params)?;
|
||||
|
||||
Ok(RaTlsCertifiedKey {
|
||||
cert_der: crt.serialize_der()?,
|
||||
key_der: crt.serialize_private_key_der(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "occlum"))]
|
||||
fn get_quote(&self, _: &KeyPair) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
Ok([0u8; 32].to_vec())
|
||||
}
|
||||
|
||||
#[cfg(feature = "occlum")]
|
||||
fn get_quote(&self, key_pair: &KeyPair) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let public_key = key_pair.public_key_raw().to_vec();
|
||||
let report_data = hash_sha512(public_key);
|
||||
let quote = SGXQuote::from_report_data(&report_data)?;
|
||||
|
||||
Ok(quote.as_slice().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl CertificateBuilder for RaTlsCertificateBuilder {
|
||||
fn build(&self) -> Result<CertifiedKey, RaTlsError> {
|
||||
self.build_internal()
|
||||
.map(|k| {
|
||||
let sign_key = any_supported_type(&PrivateKey(k.key_der)).unwrap();
|
||||
|
||||
CertifiedKey::new(vec![Certificate(k.cert_der)], sign_key)
|
||||
})
|
||||
.map_err(|e| {
|
||||
let err = RaTlsError::CertificateBuildError(e.to_string());
|
||||
error!("{}", err);
|
||||
err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RaTlsCertificate {
|
||||
fn verify_quote(&self, config: &RaTlsConfig) -> Result<(), Box<dyn Error>>;
|
||||
}
|
||||
|
||||
impl RaTlsCertificate for rustls::Certificate {
|
||||
#[cfg(not(feature = "occlum"))]
|
||||
fn verify_quote(&self, _: &RaTlsConfig) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "occlum")]
|
||||
fn verify_quote(&self, config: &RaTlsConfig) -> Result<(), Box<dyn Error>> {
|
||||
let mut parser = X509CertificateParser::new().with_deep_parse_extensions(true);
|
||||
let (_, x509) = parser.parse(self.as_ref()).unwrap();
|
||||
|
||||
let report_oid = Oid::from(&REPORT_OID).unwrap();
|
||||
|
||||
println!("{:?}", x509);
|
||||
|
||||
if let Ok(Some(report)) = x509.get_extension_unique(&report_oid) {
|
||||
let quote = SGXQuote::from_slice(report.value)?;
|
||||
|
||||
quote.verify()?;
|
||||
|
||||
let public_key = x509.public_key().parsed()?;
|
||||
let public_key = match public_key {
|
||||
public_key::PublicKey::EC(key) => key.data().to_vec(),
|
||||
_ => return Err("Unexpected public key type".into()),
|
||||
};
|
||||
|
||||
let report_data = &*quote.report_data();
|
||||
|
||||
if hash_sha512(public_key) != report_data {
|
||||
return Err("Invalid quote report data".into());
|
||||
}
|
||||
|
||||
println!("x509 pk matches report data");
|
||||
|
||||
config.is_allowed_quote("e)?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Not found quote extension".into())
|
||||
}
|
||||
}
|
||||
}
|
74
src/client.rs
Normal file
74
src/client.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
cert::{CertificateBuilder, RaTlsCertificate, RaTlsCertificateBuilder},
|
||||
RaTlsConfig, RaTlsConfigBuilder, RaTlsError,
|
||||
};
|
||||
use rustls::{
|
||||
client::{ResolvesClientCert, ServerCertVerified, ServerCertVerifier},
|
||||
sign::CertifiedKey,
|
||||
ClientConfig,
|
||||
};
|
||||
|
||||
pub struct RaTlsServerCertVerifier {
|
||||
config: RaTlsConfig,
|
||||
}
|
||||
|
||||
impl RaTlsServerCertVerifier {
|
||||
pub fn new(config: RaTlsConfig) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerCertVerifier for RaTlsServerCertVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &rustls::Certificate,
|
||||
_intermediates: &[rustls::Certificate],
|
||||
_server_name: &rustls::ServerName,
|
||||
_scts: &mut dyn Iterator<Item = &[u8]>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: std::time::SystemTime,
|
||||
) -> Result<ServerCertVerified, rustls::Error> {
|
||||
end_entity
|
||||
.verify_quote(&self.config)
|
||||
.map_err(|e| rustls::Error::General(e.to_string()))?;
|
||||
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RaTlsClientCertResolver {
|
||||
cert: Arc<CertifiedKey>,
|
||||
}
|
||||
|
||||
impl RaTlsClientCertResolver {
|
||||
pub fn new() -> Result<Self, RaTlsError> {
|
||||
let builder = RaTlsCertificateBuilder::new().with_common_name("Client".to_string());
|
||||
let cert = builder.build().map(Arc::new)?;
|
||||
Ok(Self { cert })
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolvesClientCert for RaTlsClientCertResolver {
|
||||
fn resolve(
|
||||
&self,
|
||||
_acceptable_issuers: &[&[u8]],
|
||||
_sigschemes: &[rustls::SignatureScheme],
|
||||
) -> Option<Arc<CertifiedKey>> {
|
||||
Some(self.cert.clone())
|
||||
}
|
||||
|
||||
fn has_certs(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl RaTlsConfigBuilder<ClientConfig> for ClientConfig {
|
||||
fn from_ratls_config(config: RaTlsConfig) -> Result<Self, RaTlsError> {
|
||||
Ok(Self::builder()
|
||||
.with_safe_defaults()
|
||||
.with_custom_certificate_verifier(Arc::new(RaTlsServerCertVerifier::new(config)))
|
||||
.with_client_cert_resolver(Arc::new(RaTlsClientCertResolver::new()?)))
|
||||
}
|
||||
}
|
138
src/config.rs
Normal file
138
src/config.rs
Normal file
@ -0,0 +1,138 @@
|
||||
use crate::{RaTlsConfigBuilder, RaTlsError};
|
||||
|
||||
#[cfg(feature = "occlum")]
|
||||
use occlum_sgx::SGXQuote;
|
||||
|
||||
pub use occlum_sgx::SGXMeasurement;
|
||||
use rustls::{ClientConfig, ServerConfig};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RaTlsConfig {
|
||||
#[cfg(feature = "occlum")]
|
||||
pub(crate) allowed_instances: Vec<InstanceMeasurement>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "occlum")]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct InstanceMeasurement {
|
||||
pub(crate) mrsigners: Option<Vec<SGXMeasurement>>,
|
||||
pub(crate) mrenclaves: Option<Vec<SGXMeasurement>>,
|
||||
pub(crate) product_ids: Option<Vec<u16>>,
|
||||
pub(crate) versions: Option<Vec<u16>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "occlum")]
|
||||
impl InstanceMeasurement {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_mrsigners(self, mrsigners: Vec<SGXMeasurement>) -> Self {
|
||||
Self {
|
||||
mrsigners: Some(mrsigners),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_mrenclaves(self, mrenclaves: Vec<SGXMeasurement>) -> Self {
|
||||
Self {
|
||||
mrenclaves: Some(mrenclaves),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_product_ids(self, product_ids: Vec<u16>) -> Self {
|
||||
Self {
|
||||
product_ids: Some(product_ids),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_versions(self, versions: Vec<u16>) -> Self {
|
||||
Self {
|
||||
versions: Some(versions),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_quote_measurements(&self, quote: &SGXQuote) -> bool {
|
||||
println!("mrsigner {}", quote.mrsigner());
|
||||
println!("mrenclave {}", quote.mrenclave());
|
||||
println!("productid {}", quote.product_id());
|
||||
println!("version {}", quote.version());
|
||||
println!("-----------------------------------------------------------------");
|
||||
println!("mrsigners {:?}", self.mrsigners);
|
||||
println!("mrenclaves {:?}", self.mrenclaves);
|
||||
println!("product_ids {:?}", self.product_ids);
|
||||
println!("versions {:?}", self.versions);
|
||||
|
||||
let mut result = false;
|
||||
if let Some(mrsigners) = &self.mrsigners {
|
||||
result = true;
|
||||
let value = quote.mrsigner();
|
||||
if !mrsigners.contains(&value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(mrenclaves) = &self.mrenclaves {
|
||||
result = true;
|
||||
let value = quote.mrenclave();
|
||||
if !mrenclaves.contains(&value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(product_ids) = &self.product_ids {
|
||||
result = true;
|
||||
let value = quote.product_id();
|
||||
if !product_ids.contains(&value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(versions) = &self.versions {
|
||||
result = true;
|
||||
let value = quote.version();
|
||||
if !versions.contains(&value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl RaTlsConfig {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[cfg(feature = "occlum")]
|
||||
pub fn allow_instance_measurement(mut self, instance_measurement: InstanceMeasurement) -> Self {
|
||||
self.allowed_instances.push(instance_measurement);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "occlum")]
|
||||
pub(crate) fn is_allowed_quote(&self, quote: &SGXQuote) -> Result<(), RaTlsError> {
|
||||
match self
|
||||
.allowed_instances
|
||||
.iter()
|
||||
.any(|im| im.check_quote_measurements(quote))
|
||||
{
|
||||
true => Ok(()),
|
||||
false => Err(RaTlsError::QuoteVerifyError(format!(
|
||||
"{:?} is not allowed",
|
||||
quote
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_server_config(self) -> Result<ServerConfig, RaTlsError> {
|
||||
ServerConfig::from_ratls_config(self)
|
||||
}
|
||||
|
||||
pub fn into_client_config(self) -> Result<ClientConfig, RaTlsError> {
|
||||
ClientConfig::from_ratls_config(self)
|
||||
}
|
||||
}
|
20
src/error.rs
Normal file
20
src/error.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RaTlsError {
|
||||
CertificateBuildError(String),
|
||||
QuoteVerifyError(String),
|
||||
}
|
||||
|
||||
impl Display for RaTlsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
RaTlsError::CertificateBuildError(ref message) => {
|
||||
write!(f, "CertificateBuildError: {}", message)
|
||||
}
|
||||
RaTlsError::QuoteVerifyError(ref message) => write!(f, "QuoteVerifyError: {}", message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for RaTlsError {}
|
48
src/http/actix_web.rs
Normal file
48
src/http/actix_web.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use std::net;
|
||||
|
||||
use actix_http::{body::MessageBody, Request};
|
||||
use actix_service::{IntoServiceFactory, ServiceFactory};
|
||||
use actix_web::dev::{AppConfig, Response};
|
||||
use actix_web::*;
|
||||
use rustls::ServerConfig;
|
||||
|
||||
use crate::{config::RaTlsConfig, RaTlsConfigBuilder};
|
||||
|
||||
pub trait ActixWebWithRatls<F, I, S, B>
|
||||
where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoServiceFactory<S, Request>,
|
||||
S: ServiceFactory<Request, Config = AppConfig>,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: std::fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
{
|
||||
fn bind_ratls<A: net::ToSocketAddrs>(
|
||||
self,
|
||||
addr: A,
|
||||
config: RaTlsConfig,
|
||||
) -> Result<HttpServer<F, I, S, B>, std::io::Error>;
|
||||
}
|
||||
|
||||
impl<F, I, S, B> ActixWebWithRatls<F, I, S, B> for HttpServer<F, I, S, B>
|
||||
where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoServiceFactory<S, Request>,
|
||||
S: ServiceFactory<Request, Config = AppConfig> + 'static,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: std::fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
fn bind_ratls<A: net::ToSocketAddrs>(
|
||||
self,
|
||||
addr: A,
|
||||
config: RaTlsConfig,
|
||||
) -> Result<Self, std::io::Error> {
|
||||
let config = ServerConfig::from_ratls_config(config).map_err(|e|
|
||||
std::io::Error::new(std::io::ErrorKind::Other, format!("{}", e))
|
||||
)?;
|
||||
self.bind_rustls(addr, config)
|
||||
}
|
||||
}
|
4
src/http/mod.rs
Normal file
4
src/http/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
#[cfg(feature = "actix-web")]
|
||||
pub mod actix_web;
|
||||
#[cfg(feature = "reqwest")]
|
||||
pub mod reqwest;
|
14
src/http/reqwest.rs
Normal file
14
src/http/reqwest.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use reqwest::ClientBuilder;
|
||||
use rustls::ClientConfig;
|
||||
|
||||
use crate::{RaTlsConfig, RaTlsConfigBuilder};
|
||||
|
||||
pub trait ReqwestUseRatls {
|
||||
fn use_ratls(self, config: RaTlsConfig) -> ClientBuilder;
|
||||
}
|
||||
|
||||
impl ReqwestUseRatls for ClientBuilder {
|
||||
fn use_ratls(self, config: RaTlsConfig) -> ClientBuilder {
|
||||
self.use_preconfigured_tls(ClientConfig::from_ratls_config(config).unwrap())
|
||||
}
|
||||
}
|
28
src/lib.rs
Normal file
28
src/lib.rs
Normal file
@ -0,0 +1,28 @@
|
||||
mod cert;
|
||||
mod client;
|
||||
mod config;
|
||||
mod error;
|
||||
mod http;
|
||||
mod server;
|
||||
#[cfg(feature = "occlum")]
|
||||
mod utils;
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
pub use crate::config::RaTlsConfig;
|
||||
|
||||
#[cfg(feature = "occlum")]
|
||||
pub use crate::config::InstanceMeasurement;
|
||||
|
||||
pub use crate::error::RaTlsError;
|
||||
pub use occlum_sgx::SGXMeasurement;
|
||||
|
||||
#[cfg(feature = "actix-web")]
|
||||
pub use crate::http::actix_web;
|
||||
|
||||
#[cfg(feature = "reqwest")]
|
||||
pub use crate::http::reqwest;
|
||||
|
||||
pub trait RaTlsConfigBuilder<T> {
|
||||
fn from_ratls_config(config: RaTlsConfig) -> Result<T, RaTlsError>;
|
||||
}
|
11
src/prelude.rs
Normal file
11
src/prelude.rs
Normal file
@ -0,0 +1,11 @@
|
||||
pub use crate::RaTlsConfig;
|
||||
pub use crate::SGXMeasurement;
|
||||
|
||||
#[cfg(feature = "occlum")]
|
||||
pub use crate::InstanceMeasurement;
|
||||
|
||||
#[cfg(feature = "reqwest")]
|
||||
pub use crate::reqwest::ReqwestUseRatls;
|
||||
|
||||
#[cfg(feature = "actix-web")]
|
||||
pub use crate::actix_web::ActixWebWithRatls;
|
67
src/server.rs
Normal file
67
src/server.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use rustls::{server::{ClientCertVerified, ClientCertVerifier, ResolvesServerCert}, sign::CertifiedKey, Certificate, Error, ServerConfig, DistinguishedNames};
|
||||
use std::{sync::Arc, time::SystemTime};
|
||||
|
||||
use crate::{
|
||||
cert::{CertificateBuilder, RaTlsCertificate, RaTlsCertificateBuilder},
|
||||
RaTlsConfig, RaTlsConfigBuilder, RaTlsError,
|
||||
};
|
||||
|
||||
pub struct RaTlsClientCertVerifier {
|
||||
config: RaTlsConfig,
|
||||
}
|
||||
|
||||
impl RaTlsClientCertVerifier {
|
||||
pub fn new(config: RaTlsConfig) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientCertVerifier for RaTlsClientCertVerifier {
|
||||
fn verify_client_cert(
|
||||
&self,
|
||||
end_entity: &Certificate,
|
||||
_intermediates: &[Certificate],
|
||||
_now: SystemTime,
|
||||
) -> Result<ClientCertVerified, Error> {
|
||||
end_entity.verify_quote(&self.config).map_err(|e| {
|
||||
println!("{:?}", e);
|
||||
rustls::Error::General(e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(ClientCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn client_auth_root_subjects(&self) -> Option<DistinguishedNames> {
|
||||
Some(DistinguishedNames::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RaTlsServerCertResolver {
|
||||
cert: Arc<CertifiedKey>,
|
||||
}
|
||||
|
||||
impl RaTlsServerCertResolver {
|
||||
pub fn new() -> Result<Self, RaTlsError> {
|
||||
let builder = RaTlsCertificateBuilder::new().with_common_name("Client".to_string());
|
||||
let cert = builder.build().map(Arc::new)?;
|
||||
Ok(Self { cert })
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolvesServerCert for RaTlsServerCertResolver {
|
||||
fn resolve(
|
||||
&self,
|
||||
_client_hello: rustls::server::ClientHello,
|
||||
) -> Option<std::sync::Arc<CertifiedKey>> {
|
||||
Some(self.cert.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl RaTlsConfigBuilder<ServerConfig> for ServerConfig {
|
||||
fn from_ratls_config(config: RaTlsConfig) -> Result<Self, RaTlsError> {
|
||||
Ok(Self::builder()
|
||||
.with_safe_defaults()
|
||||
.with_client_cert_verifier(Arc::new(RaTlsClientCertVerifier::new(config)))
|
||||
.with_cert_resolver(Arc::new(RaTlsServerCertResolver::new()?)))
|
||||
}
|
||||
}
|
11
src/utils.rs
Normal file
11
src/utils.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use ring::digest::{Context, SHA512};
|
||||
|
||||
pub fn hash_sha512(input: Vec<u8>) -> [u8; 64] {
|
||||
let mut context = Context::new(&SHA512);
|
||||
context.update(input.as_ref());
|
||||
let result = context.finish();
|
||||
let digest = result.as_ref();
|
||||
let mut output = [0u8; 64];
|
||||
output.copy_from_slice(digest);
|
||||
output
|
||||
}
|
Loading…
Reference in New Issue
Block a user