You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@teaclave.apache.org by ms...@apache.org on 2019/12/30 08:32:00 UTC
[incubator-teaclave] branch master updated: [attestation] Refactor
remote attestation code (#198)
This is an automated email from the ASF dual-hosted git repository.
mssun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-teaclave.git
The following commit(s) were added to refs/heads/master by this push:
new 21cf3bb [attestation] Refactor remote attestation code (#198)
21cf3bb is described below
commit 21cf3bbee75fad82cd61ce74929f6c590d91f514
Author: Mingshen Sun <bo...@mssun.me>
AuthorDate: Mon Dec 30 00:31:50 2019 -0800
[attestation] Refactor remote attestation code (#198)
- Introduce `teaclave_attestation`, a new create for attestation related code.
- This PR also refactor RA related code and move them into the `teaclave_attestation` crate.
- Create a new struct called `SgxQuoteVerifier` to wrap around `EnclaveAttr` and rewrite the original `EnclaveAttr` struct.
- For the `teaclave_attestation`, we start to try `thiserror` and `anyhow` for error management.
---
mesatee_core/Cargo.toml | 3 +-
mesatee_core/src/config/mod.rs | 13 +-
mesatee_core/src/prelude.rs | 2 +-
mesatee_core/src/rpc/channel.rs | 7 +-
mesatee_core/src/rpc/server.rs | 17 +-
mesatee_core/src/rpc/sgx/client.rs | 22 +-
mesatee_core/src/rpc/sgx/mod.rs | 122 +----
mesatee_core/src/rpc/sgx/ra.rs | 610 +--------------------
mesatee_core/src/rpc/sgx/server.rs | 14 +-
{mesatee_core => teaclave_attestation}/Cargo.toml | 42 +-
.../rpc/sgx => teaclave_attestation/src}/cert.rs | 0
teaclave_attestation/src/ias.rs | 215 ++++++++
teaclave_attestation/src/key.rs | 184 +++++++
teaclave_attestation/src/lib.rs | 49 ++
.../auth.rs => teaclave_attestation/src/quote.rs | 265 ++++-----
teaclave_attestation/src/report.rs | 199 +++++++
teaclave_attestation/src/verifier.rs | 153 ++++++
teaclave_common/mayfail/Cargo.toml | 14 +
.../fail.rs => teaclave_common/mayfail/src/lib.rs | 11 +-
teaclave_utils/src/lib.rs | 19 +-
third_party/crates-io | 2 +-
21 files changed, 1066 insertions(+), 897 deletions(-)
diff --git a/mesatee_core/Cargo.toml b/mesatee_core/Cargo.toml
index 1d3a56f..7aae459 100644
--- a/mesatee_core/Cargo.toml
+++ b/mesatee_core/Cargo.toml
@@ -12,7 +12,7 @@ path = "src/lib.rs"
[features]
default = []
-mesalock_sgx = ["sgx_tstd", "sgx_tcrypto", "sgx_rand", "sgx_tse", "ipc", "teaclave_config/mesalock_sgx", "teaclave_utils/mesalock_sgx"]
+mesalock_sgx = ["sgx_tstd", "sgx_tcrypto", "sgx_rand", "sgx_tse", "ipc", "teaclave_config/mesalock_sgx", "teaclave_utils/mesalock_sgx", "teaclave_attestation/mesalock_sgx"]
ipc = []
[dependencies]
@@ -47,3 +47,4 @@ sgx_tse = { version = "1.1.0", optional = true }
teaclave_config = { path = "../teaclave_config" }
teaclave_utils = { path = "../teaclave_utils" }
ipc_attribute = { path = "./ipc_attribute" }
+teaclave_attestation = { path = "../teaclave_attestation" }
diff --git a/mesatee_core/src/config/mod.rs b/mesatee_core/src/config/mod.rs
index f5167ca..d88b5ad 100644
--- a/mesatee_core/src/config/mod.rs
+++ b/mesatee_core/src/config/mod.rs
@@ -18,10 +18,11 @@
// ip/port is dynamically dispatched for fns client.
// we cannot use the &'static str in this struct.
-use crate::rpc::sgx::EnclaveAttr;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::prelude::v1::*;
+use teaclave_attestation;
+use teaclave_attestation::verifier::EnclaveAttr;
use teaclave_config::build_config::BUILD_CONFIG;
use teaclave_config::runtime_config;
use teaclave_config::runtime_config::RuntimeConfig;
@@ -63,7 +64,6 @@ impl OutboundDesc {
pub fn new(measures: EnclaveMeasurement) -> OutboundDesc {
OutboundDesc::Sgx(EnclaveAttr {
measures: vec![measures],
- quote_checker: universal_quote_check,
})
}
}
@@ -114,17 +114,10 @@ pub fn runtime_config() -> &'static RuntimeConfig {
.expect("Invalid runtime config, should gracefully exit during enclave_init!")
}
-fn universal_quote_check(quote: &crate::rpc::sgx::auth::SgxQuote) -> bool {
- quote.status != crate::rpc::sgx::auth::SgxQuoteStatus::UnknownBadStatus
-}
-
pub fn get_trusted_enclave_attr(service_names: Vec<&str>) -> EnclaveAttr {
let measures = service_names
.iter()
.map(|name| *ENCLAVE_IDENTITIES.get(&(*name).to_string()).unwrap())
.collect();
- EnclaveAttr {
- measures,
- quote_checker: universal_quote_check,
- }
+ EnclaveAttr { measures }
}
diff --git a/mesatee_core/src/prelude.rs b/mesatee_core/src/prelude.rs
index b10cf35..e62d5b9 100644
--- a/mesatee_core/src/prelude.rs
+++ b/mesatee_core/src/prelude.rs
@@ -22,7 +22,7 @@ cfg_if! {
// preludes provided for SGX enclave.
pub use crate::register_ecall_handler;
pub use crate::rpc::{RpcServer, RpcClient, EnclaveService};
- pub use crate::rpc::sgx::{EnclaveAttr, Pipe, PipeConfig};
+ pub use crate::rpc::sgx::{Pipe, PipeConfig};
pub use crate::ipc::{IpcSender, IpcService, IpcReceiver};
pub use crate::ipc::protos::ECallCommand;
pub use crate::ipc::protos::ecall::{
diff --git a/mesatee_core/src/rpc/channel.rs b/mesatee_core/src/rpc/channel.rs
index 02e4ebc..af3b6c7 100644
--- a/mesatee_core/src/rpc/channel.rs
+++ b/mesatee_core/src/rpc/channel.rs
@@ -25,6 +25,9 @@ use crate::Result;
use net2::TcpBuilder;
use serde::{de::DeserializeOwned, Serialize};
+use teaclave_attestation::verifier::EnclaveAttr;
+use teaclave_attestation::verifier::SgxQuoteVerifier;
+
pub struct SgxTrustedChannel<U: Serialize, V: DeserializeOwned> {
client: sgx::PipeClient<U, V>,
}
@@ -36,7 +39,7 @@ where
{
pub fn new(
addr: std::net::SocketAddr,
- enclave_attr: sgx::EnclaveAttr,
+ enclave_attr: EnclaveAttr,
) -> Result<SgxTrustedChannel<U, V>> {
let tcp_builder = TcpBuilder::new_v4()?;
tcp_builder.reuse_address(true)?;
@@ -50,7 +53,7 @@ where
)
.unwrap()
.to_owned(),
- server_attr: enclave_attr,
+ server_verifier: SgxQuoteVerifier::new(enclave_attr),
};
let client = sgx::PipeClient::<U, V>::open(config)?;
diff --git a/mesatee_core/src/rpc/server.rs b/mesatee_core/src/rpc/server.rs
index be09738..eb2f1e2 100644
--- a/mesatee_core/src/rpc/server.rs
+++ b/mesatee_core/src/rpc/server.rs
@@ -16,12 +16,13 @@
// under the License.
use crate::rpc::sgx;
-use crate::rpc::sgx::EnclaveAttr;
use crate::rpc::EnclaveService;
use crate::rpc::RpcServer;
use crate::Result;
use serde::{de::DeserializeOwned, Serialize};
use sgx_types::c_int;
+use teaclave_attestation::verifier::EnclaveAttr;
+use teaclave_attestation::verifier::SgxQuoteVerifier;
pub struct SgxTrustedServer<U, V, X>
where
@@ -41,7 +42,19 @@ where
X: EnclaveService<U, V>,
{
pub fn new(service: X, fd: c_int, client_attr: Option<EnclaveAttr>) -> Result<Self> {
- let config = sgx::PipeConfig { fd, client_attr };
+ let config = match client_attr {
+ Some(c) => {
+ let client_verifier = SgxQuoteVerifier::new(c);
+ sgx::PipeConfig {
+ fd,
+ client_verifier: Some(client_verifier),
+ }
+ }
+ _ => sgx::PipeConfig {
+ fd,
+ client_verifier: None,
+ },
+ };
Ok(Self {
config,
service,
diff --git a/mesatee_core/src/rpc/sgx/client.rs b/mesatee_core/src/rpc/sgx/client.rs
index 9c484b1..0c67e01 100644
--- a/mesatee_core/src/rpc/sgx/client.rs
+++ b/mesatee_core/src/rpc/sgx/client.rs
@@ -17,7 +17,7 @@
use std::sync::Arc;
-use crate::rpc::sgx::EnclaveAttr;
+use teaclave_attestation::verifier::SgxQuoteVerifier;
#[cfg(feature = "mesalock_sgx")]
use sgx_types::sgx_sha256_hash_t;
@@ -42,23 +42,23 @@ lazy_static! {
#[derive(Default)]
struct ClientConfigCache {
private_key_sha256: sgx_sha256_hash_t,
- target_configs: HashMap<Arc<EnclaveAttr>, Arc<rustls::ClientConfig>>,
+ target_configs: HashMap<Arc<SgxQuoteVerifier>, Arc<rustls::ClientConfig>>,
}
#[cfg(not(feature = "mesalock_sgx"))]
#[derive(Default)]
struct ClientConfigCache {
- target_configs: HashMap<Arc<EnclaveAttr>, Arc<rustls::ClientConfig>>,
+ target_configs: HashMap<Arc<SgxQuoteVerifier>, Arc<rustls::ClientConfig>>,
}
#[cfg(feature = "mesalock_sgx")]
-pub(crate) fn get_tls_config(server_attr: Arc<EnclaveAttr>) -> Arc<rustls::ClientConfig> {
+pub(crate) fn get_tls_config(server_verifier: Arc<SgxQuoteVerifier>) -> Arc<rustls::ClientConfig> {
use crate::rpc::sgx::ra::get_current_ra_credential;
let ra_credential = get_current_ra_credential();
if let Ok(cfg_cache) = CLIENT_CONFIG_CACHE.try_read() {
- if let Some(cfg) = cfg_cache.target_configs.get(&server_attr) {
+ if let Some(cfg) = cfg_cache.target_configs.get(&server_verifier) {
return cfg.clone();
}
}
@@ -70,7 +70,7 @@ pub(crate) fn get_tls_config(server_attr: Arc<EnclaveAttr>) -> Arc<rustls::Clien
client_cfg.set_single_client_cert(certs, privkey);
client_cfg
.dangerous()
- .set_certificate_verifier(server_attr.clone());
+ .set_certificate_verifier(server_verifier.clone());
client_cfg.versions.clear();
client_cfg.versions.push(rustls::ProtocolVersion::TLSv1_2);
@@ -86,16 +86,16 @@ pub(crate) fn get_tls_config(server_attr: Arc<EnclaveAttr>) -> Arc<rustls::Clien
let _ = cfg_cache
.target_configs
- .insert(server_attr, final_arc.clone());
+ .insert(server_verifier, final_arc.clone());
}
final_arc
}
#[cfg(not(feature = "mesalock_sgx"))]
-pub(crate) fn get_tls_config(server_attr: Arc<EnclaveAttr>) -> Arc<rustls::ClientConfig> {
+pub(crate) fn get_tls_config(server_verifier: Arc<SgxQuoteVerifier>) -> Arc<rustls::ClientConfig> {
if let Ok(cfg_cache) = CLIENT_CONFIG_CACHE.try_read() {
- if let Some(cfg) = cfg_cache.target_configs.get(&server_attr) {
+ if let Some(cfg) = cfg_cache.target_configs.get(&server_verifier) {
return cfg.clone();
}
}
@@ -104,7 +104,7 @@ pub(crate) fn get_tls_config(server_attr: Arc<EnclaveAttr>) -> Arc<rustls::Clien
client_cfg
.dangerous()
- .set_certificate_verifier(server_attr.clone());
+ .set_certificate_verifier(server_verifier.clone());
client_cfg.versions.clear();
client_cfg.versions.push(rustls::ProtocolVersion::TLSv1_2);
@@ -113,7 +113,7 @@ pub(crate) fn get_tls_config(server_attr: Arc<EnclaveAttr>) -> Arc<rustls::Clien
if let Ok(mut cfg_cache) = CLIENT_CONFIG_CACHE.try_write() {
let _ = cfg_cache
.target_configs
- .insert(server_attr, final_arc.clone());
+ .insert(server_verifier, final_arc.clone());
}
final_arc
diff --git a/mesatee_core/src/rpc/sgx/mod.rs b/mesatee_core/src/rpc/sgx/mod.rs
index 5bed990..127a61d 100644
--- a/mesatee_core/src/rpc/sgx/mod.rs
+++ b/mesatee_core/src/rpc/sgx/mod.rs
@@ -21,7 +21,6 @@
use serde::{de::DeserializeOwned, Serialize};
#[cfg(feature = "mesalock_sgx")]
use sgx_types::c_int;
-use std::hash::{Hash, Hasher};
use std::io::{self, Read, Write};
use std::marker::PhantomData;
use std::net::TcpStream;
@@ -36,20 +35,13 @@ use crate::rpc::{EnclaveService, RpcServer};
use crate::rpc::RpcClient;
use crate::Result;
-use teaclave_utils;
-use teaclave_utils::EnclaveMeasurement;
-
-#[macro_use]
-mod fail;
+use teaclave_attestation;
+use teaclave_attestation::verifier::SgxQuoteVerifier;
pub mod client;
#[cfg(feature = "mesalock_sgx")]
pub mod server;
-#[macro_use]
-mod cert;
-pub mod auth;
-pub use auth::*;
#[cfg(feature = "mesalock_sgx")]
mod ra;
@@ -61,113 +53,11 @@ pub fn prelude() -> Result<()> {
ra::init_ra_credential(86400u64)
}
-#[derive(Clone)]
-pub struct EnclaveAttr {
- pub measures: Vec<EnclaveMeasurement>,
- pub quote_checker: fn(&SgxQuote) -> bool,
-}
-
-impl PartialEq for EnclaveAttr {
- fn eq(&self, other: &EnclaveAttr) -> bool {
- self.quote_checker as usize == other.quote_checker as usize
- && self.measures == other.measures
- }
-}
-
-impl Eq for EnclaveAttr {}
-
-impl Hash for EnclaveAttr {
- fn hash<H: Hasher>(&self, state: &mut H) {
- for m in &self.measures {
- m.mr_enclave.hash(state);
- m.mr_signer.hash(state);
- }
- (self.quote_checker as usize).hash(state);
- }
-}
-
-impl EnclaveAttr {
- fn check_in_cert_quote(&self, cert_der: &[u8]) -> bool {
- if cfg!(sgx_sim) {
- return true;
- }
-
- let quote_result = auth::extract_sgx_quote_from_mra_cert(&cert_der);
- let quote: SgxQuote = match quote_result {
- Err(_) => {
- return false;
- }
- Ok(quote) => quote,
- };
-
- // Enclave measures are not tested in test mode since we have
- // a dedicated test enclave not known to production enclaves
- if cfg!(test_mode) {
- return (self.quote_checker)("e);
- }
-
- let this_mr_signer = "e.body.report_body.mr_signer;
- let this_mr_enclave = "e.body.report_body.mr_enclave;
-
- let checksum_match = self
- .measures
- .iter()
- .any(|m| &m.mr_signer == this_mr_signer && &m.mr_enclave == this_mr_enclave);
-
- checksum_match && (self.quote_checker)("e)
- }
-}
-
-impl rustls::ServerCertVerifier for EnclaveAttr {
- fn verify_server_cert(
- &self,
- _roots: &rustls::RootCertStore,
- certs: &[rustls::Certificate],
- _hostname: webpki::DNSNameRef,
- _ocsp: &[u8],
- ) -> std::result::Result<rustls::ServerCertVerified, rustls::TLSError> {
- // This call automatically verifies certificate signature
- if certs.len() != 1 {
- return Err(rustls::TLSError::NoCertificatesPresented);
- }
- if self.check_in_cert_quote(&certs[0].0) {
- Ok(rustls::ServerCertVerified::assertion())
- } else {
- Err(rustls::TLSError::WebPKIError(
- webpki::Error::ExtensionValueInvalid,
- ))
- }
- }
-}
-
-impl rustls::ClientCertVerifier for EnclaveAttr {
- fn client_auth_root_subjects(&self) -> rustls::DistinguishedNames {
- rustls::DistinguishedNames::new()
- }
-
- fn verify_client_cert(
- &self,
- certs: &[rustls::Certificate],
- ) -> std::result::Result<rustls::ClientCertVerified, rustls::TLSError> {
- // This call automatically verifies certificate signature
- if certs.len() != 1 {
- return Err(rustls::TLSError::NoCertificatesPresented);
- }
- if self.check_in_cert_quote(&certs[0].0) {
- Ok(rustls::ClientCertVerified::assertion())
- } else {
- Err(rustls::TLSError::WebPKIError(
- webpki::Error::ExtensionValueInvalid,
- ))
- }
- }
-}
-
#[cfg(feature = "mesalock_sgx")]
pub struct PipeConfig {
pub fd: c_int,
// the SGX server can optionally verify the identity of the client
- pub client_attr: Option<EnclaveAttr>,
+ pub client_verifier: Option<SgxQuoteVerifier>,
}
#[cfg(feature = "mesalock_sgx")]
@@ -215,7 +105,7 @@ where
// TODO: Due to switching to the SDK-style design, performing an
// initial RA at enclave start is not longer a viable design. Need
// to refactor the related API.
- let rustls_server_cfg = server::get_tls_config(&config.client_attr)?;
+ let rustls_server_cfg = server::get_tls_config(&config.client_verifier)?;
let sess = rustls::ServerSession::new(&rustls_server_cfg);
Ok(Pipe {
@@ -239,7 +129,7 @@ pub struct PipeClient<U, V> {
pub struct PipeClientConfig {
pub tcp: TcpStream,
pub hostname: webpki::DNSName,
- pub server_attr: EnclaveAttr,
+ pub server_verifier: SgxQuoteVerifier,
}
impl<U, V> Read for PipeClient<U, V> {
@@ -265,7 +155,7 @@ where
{
type Config = PipeClientConfig;
fn open(config: Self::Config) -> Result<Self> {
- let rustls_client_cfg = client::get_tls_config(Arc::new(config.server_attr));
+ let rustls_client_cfg = client::get_tls_config(Arc::new(config.server_verifier));
let sess = rustls::ClientSession::new(&rustls_client_cfg, config.hostname.as_ref());
Ok(PipeClient {
diff --git a/mesatee_core/src/rpc/sgx/ra.rs b/mesatee_core/src/rpc/sgx/ra.rs
index c94da09..eb97a76 100644
--- a/mesatee_core/src/rpc/sgx/ra.rs
+++ b/mesatee_core/src/rpc/sgx/ra.rs
@@ -20,68 +20,19 @@
// This entire file is solely used for the sgx environment
use std::prelude::v1::*;
-use base64;
-use bit_vec;
-use chrono;
-use httparse;
-use num_bigint;
-use rustls;
-use webpki;
-use webpki_roots;
-use yasna;
-
-use sgx_rand::os::SgxRng;
-use sgx_rand::Rng;
-use sgx_tcrypto::*;
-use sgx_tse::*;
+use sgx_tcrypto::rsgx_sha256_slice;
use sgx_types::*;
-use std::io::{Read, Write};
-use std::net::TcpStream;
-use std::ptr;
-use std::sync::{Arc, SgxRwLock};
+use std::sync::SgxRwLock;
use std::time::{self, SystemTime};
use std::untrusted::time::SystemTimeEx;
use lazy_static::lazy_static;
-use super::fail::MayfailTrace;
use crate::{Error, ErrorKind, Result};
use crate::config::runtime_config;
-use teaclave_utils;
-
-pub const CERT_VALID_DAYS: i64 = 90i64;
-
-extern "C" {
- fn ocall_sgx_init_quote(
- p_retval: *mut sgx_status_t,
- p_target_info: *mut sgx_target_info_t,
- p_gid: *mut sgx_epid_group_id_t,
- ) -> sgx_status_t;
-
- fn ocall_sgx_get_ias_socket(p_retval: *mut i32) -> sgx_status_t;
-
- fn ocall_sgx_calc_quote_size(
- p_retval: *mut sgx_status_t,
- p_sig_rl: *const u8,
- sig_rl_size: u32,
- p_quote_size: *mut u32,
- ) -> sgx_status_t;
-
- fn ocall_sgx_get_quote(
- p_retval: *mut sgx_status_t,
- p_report: *const sgx_report_t,
- quote_type: sgx_quote_sign_type_t,
- p_spid: *const sgx_spid_t,
- p_nonce: *const sgx_quote_nonce_t,
- p_sig_rl: *const u8,
- sig_rl_size: u32,
- p_qe_report: *mut sgx_report_t,
- p_quote: *mut u8,
- quote_size: u32,
- ) -> sgx_status_t;
-}
+use teaclave_attestation;
lazy_static! {
static ref RACACHE: SgxRwLock<RACache> = {
@@ -91,17 +42,6 @@ lazy_static! {
validity: time::Duration::from_secs(0),
})
};
-
- static ref IAS_CLIENT_CONFIG: Arc<rustls::ClientConfig> = {
- let mut config = rustls::ClientConfig::new();
-
- // We trust known CA
- config
- .root_store
- .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
-
- Arc::new(config)
- };
}
/// Certificate and public key in DER format
@@ -119,11 +59,6 @@ struct RACache {
validity: time::Duration,
}
-struct Secp256k1KeyPair {
- prv_k: sgx_ec256_private_t,
- pub_k: sgx_ec256_public_t,
-}
-
pub(crate) fn init_ra_credential(valid_secs: u64) -> Result<()> {
match RACache::new(valid_secs) {
Ok(new_entry) => {
@@ -193,11 +128,25 @@ pub(crate) fn get_current_ra_credential() -> RACredential {
impl RACredential {
fn generate_and_endorse() -> Result<RACredential> {
- let (prv_k, pub_k) = generate_sgx_ecc_keypair()?;
- let report = create_attestation_report(&pub_k)?;
- let payload = [report.report, report.signature, report.certificate].join("|");
-
- let key_pair = Secp256k1KeyPair::new(prv_k, pub_k);
+ let key_pair = teaclave_attestation::key::Secp256k1KeyPair::new()
+ .map_err(|_| Error::from(ErrorKind::RAInternalError))?;
+ let report = if cfg!(sgx_sim) {
+ teaclave_attestation::IasReport::default()
+ } else {
+ match teaclave_attestation::IasReport::new(
+ key_pair.pub_k,
+ &runtime_config().env.ias_key,
+ &runtime_config().env.ias_spid,
+ false,
+ ) {
+ Ok(r) => r,
+ Err(e) => {
+ error!("{:?}", e);
+ return Err(Error::from(ErrorKind::RAInternalError));
+ }
+ }
+ };
+ let payload = [report.report, report.signature, report.signing_cert].join("|");
let cert_der =
key_pair.create_cert_with_extension("Teaclave", "Teaclave", &payload.as_bytes());
let prv_key_der = key_pair.private_key_into_der();
@@ -228,518 +177,3 @@ impl RACache {
dur.is_ok() && dur.unwrap() < self.validity
}
}
-
-fn generate_sgx_ecc_keypair() -> Result<(sgx_ec256_private_t, sgx_ec256_public_t)> {
- let ecc_handle = SgxEccHandle::new();
- ecc_handle.open()?;
- let (prv_k, pub_k) = ecc_handle.create_key_pair()?;
- ecc_handle.close()?;
- Ok((prv_k, pub_k))
-}
-
-impl Secp256k1KeyPair {
- fn new(prv_k: sgx_ec256_private_t, pub_k: sgx_ec256_public_t) -> Self {
- Self { prv_k, pub_k }
- }
-
- pub fn private_key_into_der(&self) -> Vec<u8> {
- use bit_vec::BitVec;
- use yasna::construct_der;
- use yasna::models::ObjectIdentifier;
- use yasna::Tag;
-
- let ec_public_key_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 2, 1]);
- let prime256v1_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 3, 1, 7]);
-
- let pub_key_bytes = self.public_key_into_bytes();
- let prv_key_bytes = self.private_key_into_bytes();
-
- // Construct private key in DER.
- construct_der(|writer| {
- writer.write_sequence(|writer| {
- writer.next().write_u8(0);
- writer.next().write_sequence(|writer| {
- writer.next().write_oid(&ec_public_key_oid);
- writer.next().write_oid(&prime256v1_oid);
- });
- let inner_key_der = construct_der(|writer| {
- writer.write_sequence(|writer| {
- writer.next().write_u8(1);
- writer.next().write_bytes(&prv_key_bytes);
- writer.next().write_tagged(Tag::context(1), |writer| {
- writer.write_bitvec(&BitVec::from_bytes(&pub_key_bytes));
- });
- });
- });
- writer.next().write_bytes(&inner_key_der);
- });
- })
- }
-
- pub fn create_cert_with_extension(
- &self,
- issuer: &str,
- subject: &str,
- payload: &[u8],
- ) -> Vec<u8> {
- use super::cert::*;
- use bit_vec::BitVec;
- use chrono::TimeZone;
- use num_bigint::BigUint;
- use std::time::UNIX_EPOCH;
- use yasna::construct_der;
- use yasna::models::ObjectIdentifier;
- use yasna::models::UTCTime;
-
- let ecdsa_with_sha256_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 4, 3, 2]);
- let common_name_oid = ObjectIdentifier::from_slice(&[2, 5, 4, 3]);
- let ec_public_key_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 2, 1]);
- let prime256v1_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 3, 1, 7]);
- let comment_oid = ObjectIdentifier::from_slice(&[2, 16, 840, 1, 113730, 1, 13]);
-
- let pub_key_bytes = self.public_key_into_bytes();
-
- // UNIX_EPOCH is the earliest time stamp. This unwrap should constantly succeed.
- let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
- let issue_ts = chrono::Utc.timestamp(now.as_secs() as i64, 0);
-
- // This is guaranteed to be a valid duration.
- let expire = now + chrono::Duration::days(CERT_VALID_DAYS).to_std().unwrap();
- let expire_ts = chrono::Utc.timestamp(expire.as_secs() as i64, 0);
-
- // Construct certificate with payload in extension in DER.
- let tbs_cert_der = construct_der(|writer| {
- let version = 2i8;
- let serial = 1u8;
- let cert_sign_algo = asn1_seq!(ecdsa_with_sha256_oid.clone());
- let issuer = asn1_seq!(asn1_seq!(asn1_seq!(
- common_name_oid.clone(),
- issuer.to_owned()
- )));
- let valid_range = asn1_seq!(
- UTCTime::from_datetime(&issue_ts),
- UTCTime::from_datetime(&expire_ts),
- );
- let subject = asn1_seq!(asn1_seq!(asn1_seq!(
- common_name_oid.clone(),
- subject.to_string(),
- )));
- let pub_key = asn1_seq!(
- asn1_seq!(ec_public_key_oid, prime256v1_oid,),
- BitVec::from_bytes(&pub_key_bytes),
- );
- let sgx_ra_cert_ext = asn1_seq!(asn1_seq!(comment_oid, payload.to_owned()));
- let tbs_cert = asn1_seq!(
- version,
- serial,
- cert_sign_algo,
- issuer,
- valid_range,
- subject,
- pub_key,
- sgx_ra_cert_ext,
- );
- TbsCert::dump(writer, tbs_cert);
- });
-
- // There will be serious problems if this call fails. We might as well
- // panic in this case, thus unwrap()
- let ecc_handle = SgxEccHandle::new();
- ecc_handle.open().unwrap();
-
- let sig = ecc_handle
- .ecdsa_sign_slice(&tbs_cert_der.as_slice(), &self.prv_k)
- .unwrap();
-
- let sig_der = yasna::construct_der(|writer| {
- writer.write_sequence(|writer| {
- let mut sig_x = sig.x;
- sig_x.reverse();
- let mut sig_y = sig.y;
- sig_y.reverse();
- writer.next().write_biguint(&BigUint::from_slice(&sig_x));
- writer.next().write_biguint(&BigUint::from_slice(&sig_y));
- });
- });
-
- yasna::construct_der(|writer| {
- writer.write_sequence(|writer| {
- writer.next().write_der(&tbs_cert_der.as_slice());
- CertSignAlgo::dump(writer.next(), asn1_seq!(ecdsa_with_sha256_oid.clone()));
- writer
- .next()
- .write_bitvec(&BitVec::from_bytes(&sig_der.as_slice()));
- });
- })
- }
-
- fn public_key_into_bytes(&self) -> Vec<u8> {
- // The first byte must be 4, which indicates the uncompressed encoding.
- let mut pub_key_bytes: Vec<u8> = vec![4];
- pub_key_bytes.extend(self.pub_k.gx.iter().rev());
- pub_key_bytes.extend(self.pub_k.gy.iter().rev());
- pub_key_bytes
- }
-
- fn private_key_into_bytes(&self) -> Vec<u8> {
- let mut prv_key_bytes: Vec<u8> = vec![];
- prv_key_bytes.extend(self.prv_k.r.iter().rev());
- prv_key_bytes
- }
-}
-
-trait MayfailTraceForHttparseStatus<T> {
- fn to_mt_result(self: Self, file: &'static str, line: u32) -> Result<T>;
-}
-
-impl<T> MayfailTraceForHttparseStatus<T> for httparse::Status<T> {
- fn to_mt_result(self: Self, file: &'static str, line: u32) -> Result<T> {
- match self {
- httparse::Status::Complete(v) => Ok(v),
- httparse::Status::Partial => {
- debug!("error at {}:{}", file, line);
- Err(Error::unknown())
- }
- }
- }
-}
-
-pub const DEV_HOSTNAME: &str = "api.trustedservices.intel.com";
-// pub const PROD_HOSTNAME: &'static str = "as.sgx.trustedservices.intel.com";
-pub const SIGRL_SUFFIX: &str = "/sgx/dev/attestation/v3/sigrl/";
-pub const REPORT_SUFFIX: &str = "/sgx/dev/attestation/v3/report";
-
-fn sanitize_http_response(respp: &httparse::Response) -> Result<()> {
- if let Some(code) = respp.code {
- if code != 200 {
- error!("Intel IAS service returned invalid HTTP {}", code);
- Err(Error::from(ErrorKind::RAInternalError))
- } else {
- Ok(())
- }
- } else {
- error!("Intel IAS service returned invalid HTTP response");
- Err(Error::from(ErrorKind::RAInternalError))
- }
-}
-
-struct AttnReport {
- pub report: String,
- pub signature: String,
- pub certificate: String,
-}
-
-fn parse_response_attn_report(resp: &[u8]) -> Result<AttnReport> {
- let mut headers = [httparse::EMPTY_HEADER; 16];
- let mut respp = httparse::Response::new(&mut headers);
- let result = respp.parse(resp);
-
- sanitize_http_response(&respp)?;
-
- let mut sig: Result<String> = Err(Error::from(ErrorKind::MissingValue));
- let mut sig_cert: Result<String> = Err(Error::from(ErrorKind::MissingValue));
- let mut attn_report: Result<String> = Err(Error::from(ErrorKind::MissingValue));
-
- for header in respp.headers {
- match header.name {
- "Content-Length" => {
- let len_num = mayfail! {
- len_str =<< std::str::from_utf8(header.value);
- n =<< len_str.parse::<u32>();
- ret n
- };
-
- if len_num.unwrap_or(0) > 0 {
- attn_report = mayfail! {
- status =<< result;
- header_len =<< status;
- let resp_body = &resp[header_len..];
- report_str =<< std::str::from_utf8(resp_body);
- ret report_str.to_string()
- }
- }
- }
- "X-IASReport-Signature" => {
- sig = mayfail! {
- sig =<< std::str::from_utf8(header.value);
- ret sig.to_string()
- }
- }
- "X-IASReport-Signing-Certificate" => {
- sig_cert = mayfail! {
- cert_str =<< std::str::from_utf8(header.value);
- // Remove %0A from cert, and only obtain the signing cert
- let cert = cert_str.to_string().replace("%0A", "");
- // We should get two concatenated PEM files at this step
- decoded_cert =<< teaclave_utils::percent_decode(cert);
- let cert_content: Vec<&str> = decoded_cert.split("-----").collect();
- _ =<< if cert_content.len() != 9 { None } else { Some(()) };
- ret cert_content[2].to_string()
- }
- }
- _ => (),
- }
- }
-
- mayfail! {
- ret_sig =<< sig;
- ret_sig_cert =<< sig_cert;
- ret_attn_report =<< attn_report;
- ret AttnReport {
- report: ret_attn_report,
- signature: ret_sig,
- certificate: ret_sig_cert,
- }
- }
-}
-
-fn parse_response_sigrl(resp: &[u8]) -> Result<Vec<u8>> {
- let mut headers = [httparse::EMPTY_HEADER; 16];
- let mut respp = httparse::Response::new(&mut headers);
- let result = respp.parse(resp);
-
- sanitize_http_response(&respp)?;
-
- let len_num = mayfail! {
- header =<< respp.headers.iter().find(|&&header| header.name == "Content-Length");
- len_str =<< std::str::from_utf8(header.value);
- len_num =<< len_str.parse::<u32>();
- ret len_num
- };
-
- len_num.and_then(|len| -> Result<Vec<u8>> {
- if len == 0 {
- Ok(Vec::new())
- } else {
- mayfail! {
- status =<< result;
- header_len =<< status;
- let resp_body = &resp[header_len..];
- base64 =<< std::str::from_utf8(resp_body);
- decoded =<< base64::decode(base64);
- ret decoded
- }
- }
- })
-}
-
-fn talk_to_intel_ias(fd: c_int, req: String) -> Result<Vec<u8>> {
- mayfail! {
- dns_name =<< webpki::DNSNameRef::try_from_ascii_str(DEV_HOSTNAME);
- let mut sess = rustls::ClientSession::new(&IAS_CLIENT_CONFIG, dns_name);
- mut sock =<< TcpStream::new(fd);
- let mut tls = rustls::Stream::new(&mut sess, &mut sock);
- _ =<< tls.write(req.as_bytes());
- let mut plaintext = Vec::new();
- _ =<< tls.read_to_end(&mut plaintext);
- ret plaintext
- }
-}
-
-fn get_sigrl_from_intel(fd: c_int, gid: u32) -> Result<Vec<u8>> {
- let req = format!(
- "GET {}{:08x} HTTP/1.1\r\nHOST: {}\r\nOcp-Apim-Subscription-Key: {}\r\nConnection: Close\r\n\r\n",
- SIGRL_SUFFIX, gid, DEV_HOSTNAME, &runtime_config().env.ias_key
- );
-
- mayfail! {
- plaintext =<< talk_to_intel_ias(fd, req);
- sigrl =<< parse_response_sigrl(&plaintext);
- ret sigrl
- }
-}
-
-// TODO: support pse
-fn get_report_from_intel(fd: c_int, quote: &[u8]) -> Result<AttnReport> {
- let encoded_quote = base64::encode(quote);
- let encoded_json = format!("{{\"isvEnclaveQuote\":\"{}\"}}\r\n", encoded_quote);
-
- let req = format!("POST {} HTTP/1.1\r\nHOST: {}\r\nOcp-Apim-Subscription-Key: {}\r\nConnection: Close\r\nContent-Length:{}\r\nContent-Type: application/json\r\n\r\n{}",
- REPORT_SUFFIX,
- DEV_HOSTNAME,
- &runtime_config().env.ias_key,
- encoded_json.len(),
- encoded_json);
-
- let plaintext = talk_to_intel_ias(fd, req)?;
-
- parse_response_attn_report(&plaintext)
-}
-
-fn create_attestation_report(pub_k: &sgx_ec256_public_t) -> Result<AttnReport> {
- if cfg!(sgx_sim) {
- return Ok(AttnReport {
- report: String::from(""),
- signature: String::from(""),
- certificate: String::from(""),
- });
- }
- // Workflow:
- // (1) ocall to get the target_info structure (ti) and epid group id (eg)
- // (1.5) get sigrl
- // (2) call sgx_create_report with ti+data, produce an sgx_report_t
- // (3) ocall to sgx_get_quote to generate (*mut sgx-quote_t, uint32_t)
-
- // (1) get ti + eg
- let mut ti: sgx_target_info_t = sgx_target_info_t::default();
- let mut eg: sgx_epid_group_id_t = sgx_epid_group_id_t::default();
- let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED;
-
- let res = unsafe {
- ocall_sgx_init_quote(
- &mut rt as *mut sgx_status_t,
- &mut ti as *mut sgx_target_info_t,
- &mut eg as *mut sgx_epid_group_id_t,
- )
- };
-
- if res != sgx_status_t::SGX_SUCCESS || rt != sgx_status_t::SGX_SUCCESS {
- return Err(Error::from(ErrorKind::OCallError));
- }
-
- let eg_num = u32::from_le_bytes(eg);
-
- // (1.5) get sigrl
- let mut ias_sock: i32 = -1i32;
-
- let mut sigrl_vec: Vec<u8> = Vec::new();
- let mut sigrl_acquired: bool = false;
- for _ in 0..3 {
- let res = unsafe { ocall_sgx_get_ias_socket(&mut ias_sock as *mut i32) };
-
- debug!("got ias_sock = {}", ias_sock);
-
- if res != sgx_status_t::SGX_SUCCESS || ias_sock < 0 {
- return Err(Error::from(ErrorKind::OCallError));
- }
-
- // Now sigrl_vec is the revocation list, a vec<u8>
- match get_sigrl_from_intel(ias_sock, eg_num) {
- Ok(v) => {
- sigrl_vec = v;
- sigrl_acquired = true;
- break;
- }
- Err(_) => {
- debug!("get sirl failed, retry...");
- }
- }
- }
-
- if !sigrl_acquired {
- debug!("Cannot acquire sigrl from Intel for three times");
- return Err(Error::unknown());
- }
-
- // (2) Generate the report
- // Fill ecc256 public key into report_data
- let mut report_data: sgx_report_data_t = sgx_report_data_t::default();
- let mut pub_k_gx = pub_k.gx;
- pub_k_gx.reverse();
- let mut pub_k_gy = pub_k.gy;
- pub_k_gy.reverse();
- report_data.d[..32].clone_from_slice(&pub_k_gx);
- report_data.d[32..].clone_from_slice(&pub_k_gy);
-
- let rep = mayfail! {
- rep =<< rsgx_create_report(&ti, &report_data);
- ret rep
- }?;
-
- let mut quote_nonce = sgx_quote_nonce_t { rand: [0; 16] };
- let mut os_rng = mayfail! {
- rng =<< SgxRng::new();
- ret rng
- }?;
-
- os_rng.fill_bytes(&mut quote_nonce.rand);
- let mut qe_report = sgx_report_t::default();
-
- // (3) Generate the quote
- // Args:
- // 1. sigrl: ptr + len
- // 2. report: ptr 432bytes
- // 3. linkable: u32, unlinkable=0, linkable=1
- // 4. spid: sgx_spid_t ptr 16bytes
- // 5. sgx_quote_nonce_t ptr 16bytes
- // 6. p_sig_rl + sigrl size ( same to sigrl)
- // 7. [out]p_qe_report need further check
- // 8. [out]p_quote
- // 9. quote_size
- let (p_sigrl, sigrl_len) = if sigrl_vec.is_empty() {
- (ptr::null(), 0)
- } else {
- (sigrl_vec.as_ptr(), sigrl_vec.len() as u32)
- };
- let p_report = &rep as *const sgx_report_t;
- let quote_type = sgx_quote_sign_type_t::SGX_LINKABLE_SIGNATURE;
- let spid: sgx_spid_t = teaclave_utils::decode_spid(&runtime_config().env.ias_spid)?;
- let p_spid = &spid as *const sgx_spid_t;
- let p_nonce = "e_nonce as *const sgx_quote_nonce_t;
- let p_qe_report = &mut qe_report as *mut sgx_report_t;
- let mut quote_len: u32 = 0;
-
- let res =
- unsafe { ocall_sgx_calc_quote_size(&mut rt as _, p_sigrl, sigrl_len, &mut quote_len as _) };
-
- if res != sgx_status_t::SGX_SUCCESS || rt != sgx_status_t::SGX_SUCCESS {
- return Err(Error::from(ErrorKind::OCallError));
- }
-
- let mut quote = vec![0; quote_len as usize];
- let p_quote = quote.as_mut_ptr();
-
- let res = unsafe {
- ocall_sgx_get_quote(
- &mut rt as _,
- p_report,
- quote_type,
- p_spid,
- p_nonce,
- p_sigrl,
- sigrl_len,
- p_qe_report,
- p_quote,
- quote_len,
- )
- };
-
- if res != sgx_status_t::SGX_SUCCESS || rt != sgx_status_t::SGX_SUCCESS {
- return Err(Error::from(ErrorKind::OCallError));
- }
-
- // Perform a check on qe_report to verify if the qe_report is valid
- rsgx_verify_report(&qe_report).to_mt_result(file!(), line!())?;
-
- // Check if the qe_report is produced on the same platform
- if ti.mr_enclave.m != qe_report.body.mr_enclave.m
- || ti.attributes.flags != qe_report.body.attributes.flags
- || ti.attributes.xfrm != qe_report.body.attributes.xfrm
- {
- return Err(Error::unknown());
- }
-
- // Check qe_report to defend against replay attack
- // The purpose of p_qe_report is for the ISV enclave to confirm the QUOTE
- // it received is not modified by the untrusted SW stack, and not a replay.
- // The implementation in QE is to generate a REPORT targeting the ISV
- // enclave (target info from p_report) , with the lower 32Bytes in
- // report.data = SHA256(p_nonce||p_quote). The ISV enclave can verify the
- // p_qe_report and report.data to confirm the QUOTE has not be modified and
- // is not a replay. It is optional.
- let mut rhs_vec: Vec<u8> = quote_nonce.rand.to_vec();
- rhs_vec.extend("e);
- let rhs_hash = rsgx_sha256_slice(&rhs_vec).to_mt_result(file!(), line!())?;
- let lhs_hash = &qe_report.body.report_data.d[..32];
- if rhs_hash != lhs_hash {
- return Err(Error::unknown());
- }
-
- let res = unsafe { ocall_sgx_get_ias_socket(&mut ias_sock as _) };
-
- if res != sgx_status_t::SGX_SUCCESS || ias_sock < 0 {
- return Err(Error::from(ErrorKind::OCallError));
- }
-
- get_report_from_intel(ias_sock, "e)
-}
diff --git a/mesatee_core/src/rpc/sgx/server.rs b/mesatee_core/src/rpc/sgx/server.rs
index b9c8bfd..d826edb 100644
--- a/mesatee_core/src/rpc/sgx/server.rs
+++ b/mesatee_core/src/rpc/sgx/server.rs
@@ -18,10 +18,10 @@
use std::collections::HashMap;
use std::sync::Arc;
-use crate::rpc::sgx::EnclaveAttr;
use crate::Error;
use crate::ErrorKind;
use crate::Result;
+use teaclave_attestation::verifier::SgxQuoteVerifier;
use sgx_types::sgx_sha256_hash_t;
use std::sync::SgxRwLock as RwLock;
@@ -36,17 +36,17 @@ lazy_static! {
#[derive(Default)]
struct ServerConfigCache {
private_key_sha256: sgx_sha256_hash_t,
- target_configs: HashMap<Arc<EnclaveAttr>, Arc<rustls::ServerConfig>>,
+ target_configs: HashMap<Arc<SgxQuoteVerifier>, Arc<rustls::ServerConfig>>,
}
pub(crate) fn get_tls_config(
- client_attr: &Option<EnclaveAttr>,
+ client_verifier: &Option<SgxQuoteVerifier>,
) -> Result<Arc<rustls::ServerConfig>> {
use crate::rpc::sgx::ra::get_current_ra_credential;
let ra_credential = get_current_ra_credential();
- let client_attr = match client_attr {
+ let client_verifier = match client_verifier {
Some(attr) => Arc::new(attr.clone()),
None => {
let certs = vec![rustls::Certificate(ra_credential.cert)];
@@ -61,7 +61,7 @@ pub(crate) fn get_tls_config(
};
if let Ok(cfg_cache) = SERVER_CONFIG_CACHE.try_read() {
- if let Some(cfg) = cfg_cache.target_configs.get(&client_attr) {
+ if let Some(cfg) = cfg_cache.target_configs.get(&client_verifier) {
// Hit Cache. Be quick!
return Ok(cfg.clone());
}
@@ -70,7 +70,7 @@ pub(crate) fn get_tls_config(
let certs = vec![rustls::Certificate(ra_credential.cert)];
let privkey = rustls::PrivateKey(ra_credential.private_key);
- let mut server_cfg = rustls::ServerConfig::new(client_attr.clone());
+ let mut server_cfg = rustls::ServerConfig::new(client_verifier.clone());
server_cfg
.set_single_cert(certs, privkey)
.map_err(|_| Error::from(ErrorKind::TLSError))?;
@@ -86,7 +86,7 @@ pub(crate) fn get_tls_config(
}
let _ = cfg_cache
.target_configs
- .insert(client_attr, final_arc.clone()); // Overwrite
+ .insert(client_verifier, final_arc.clone()); // Overwrite
}
Ok(final_arc)
diff --git a/mesatee_core/Cargo.toml b/teaclave_attestation/Cargo.toml
similarity index 52%
copy from mesatee_core/Cargo.toml
copy to teaclave_attestation/Cargo.toml
index 1d3a56f..628c1e8 100644
--- a/mesatee_core/Cargo.toml
+++ b/teaclave_attestation/Cargo.toml
@@ -1,49 +1,41 @@
[package]
-name = "mesatee_core"
+name = "teaclave_attestation"
version = "0.1.0"
authors = ["MesaTEE Authors <de...@mesatee.org>"]
-description = "Core of MesaTEE, including IPC/RPC/Error-handling/Database/etc. -- everything you need to develop a TEE services and clients"
+description = "Teaclave Attestation"
license = "Apache-2.0"
edition = "2018"
[lib]
-name = "mesatee_core"
+name = "teaclave_attestation"
path = "src/lib.rs"
[features]
default = []
-mesalock_sgx = ["sgx_tstd", "sgx_tcrypto", "sgx_rand", "sgx_tse", "ipc", "teaclave_config/mesalock_sgx", "teaclave_utils/mesalock_sgx"]
-ipc = []
+mesalock_sgx = ["sgx_tstd", "sgx_tcrypto", "sgx_rand", "sgx_tse"]
[dependencies]
+anyhow = { version = "1.0.26" }
+base64 = { version = "0.10.1" }
+bit-vec = { version = "0.6.1", default-features = false }
cfg-if = { version = "0.1.9" }
-env_logger = { version = "0.7.1" }
-lazy_static = { version = "1.0.2", features = ["spin_no_std"] }
+chrono = { version = "0.4.6" }
+httparse = { version = "1.3.2", default-features = false }
log = { version = "0.4.6" }
+num-bigint = { version = "0.2.2" }
rustls = { version = "0.16.0", features = ["dangerous_configuration"] }
-serde = { version = "1.0.92" }
-serde_derive = { version = "1.0.92" }
serde_json = { version = "1.0.39" }
-chrono = { version = "0.4.6" }
-ring = { version = "0.16.5" }
+thiserror = { version = "1.0.9" }
+uuid = { version = "0.7.4", features = ["v4"] }
webpki = { version = "0.21.0" }
webpki-roots = { version = "0.17.0" }
-base64 = { version = "0.10.1" }
yasna = { version = "0.3.0", features = ["bit-vec", "num-bigint", "chrono"] }
-num-bigint = { version = "0.2.2" }
-bit-vec = { version = "0.6.1", default-features = false }
-httparse = { version = "1.3.2", default-features = false }
-uuid = { version = "0.7.4", features = ["v4"] }
-net2 = { version = "0.2.33" }
-toml = { version = "0.5.3" }
-sgx_tstd = { version = "1.1.0", features = ["net", "backtrace"], optional = true }
-sgx_types = { version = "1.1.0" }
-sgx_urts = { version = "1.1.0" }
+sgx_rand = { version = "1.1.0", optional = true }
sgx_tcrypto = { version = "1.1.0", optional = true }
-sgx_rand = { version = "1.1.0", optional = true }
-sgx_tse = { version = "1.1.0", optional = true }
+sgx_tse = { version = "1.1.0", optional = true }
+sgx_tstd = { version = "1.1.0", features = ["net", "backtrace"], optional = true }
+sgx_types = { version = "1.1.0" }
teaclave_config = { path = "../teaclave_config" }
-teaclave_utils = { path = "../teaclave_utils" }
-ipc_attribute = { path = "./ipc_attribute" }
+teaclave_utils = { path = "../teaclave_utils" }
diff --git a/mesatee_core/src/rpc/sgx/cert.rs b/teaclave_attestation/src/cert.rs
similarity index 100%
rename from mesatee_core/src/rpc/sgx/cert.rs
rename to teaclave_attestation/src/cert.rs
diff --git a/teaclave_attestation/src/ias.rs b/teaclave_attestation/src/ias.rs
new file mode 100644
index 0000000..cbfdcf2
--- /dev/null
+++ b/teaclave_attestation/src/ias.rs
@@ -0,0 +1,215 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 crate::report::IasReport;
+use crate::AttestationError;
+use anyhow::Error;
+use anyhow::Result;
+use log::debug;
+use sgx_types::*;
+use std::collections::HashMap;
+use std::io::{Read, Write};
+use std::net::TcpStream;
+use std::prelude::v1::*;
+use std::sync::Arc;
+use teaclave_utils;
+
+extern "C" {
+ fn ocall_sgx_get_ias_socket(p_retval: *mut i32) -> sgx_status_t;
+}
+
+pub struct IasClient {
+ ias_key: String,
+ ias_hostname: &'static str,
+}
+
+impl IasClient {
+ pub fn new(ias_key: &str, production: bool) -> Self {
+ let ias_hostname = if production {
+ "as.sgx.trustedservices.intel.com"
+ } else {
+ "api.trustedservices.intel.com"
+ };
+
+ Self {
+ ias_key: ias_key.to_owned(),
+ ias_hostname,
+ }
+ }
+
+ fn get_ias_socket() -> Result<c_int> {
+ debug!("get_ias_socket");
+ let mut fd: i32 = -1i32;
+ let res = unsafe { ocall_sgx_get_ias_socket(&mut fd as _) };
+
+ if res != sgx_status_t::SGX_SUCCESS || fd < 0 {
+ Err(Error::new(AttestationError::OCallError))
+ } else {
+ Ok(fd)
+ }
+ }
+
+ fn new_tls_stream(&self) -> Result<rustls::StreamOwned<rustls::ClientSession, TcpStream>> {
+ let fd = Self::get_ias_socket()?;
+ let dns_name = webpki::DNSNameRef::try_from_ascii_str(self.ias_hostname)?;
+ let mut config = rustls::ClientConfig::new();
+ config
+ .root_store
+ .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
+ let client = rustls::ClientSession::new(&Arc::new(config), dns_name);
+ let socket = TcpStream::new(fd)?;
+ let stream = rustls::StreamOwned::new(client, socket);
+
+ Ok(stream)
+ }
+
+ pub fn get_sigrl(&mut self, epid_group_id: u32) -> Result<Vec<u8>> {
+ let sigrl_uri = format!("/sgx/dev/attestation/v3/sigrl/{:08x}", epid_group_id);
+ let request = format!(
+ "GET {} HTTP/1.1\r\n\
+ HOST: {}\r\n\
+ Ocp-Apim-Subscription-Key: {}\r\n\
+ Connection: Close\r\n\r\n",
+ sigrl_uri, self.ias_hostname, self.ias_key
+ );
+
+ let mut stream = self.new_tls_stream()?;
+ stream.write_all(request.as_bytes())?;
+ let mut response = Vec::new();
+ stream.read_to_end(&mut response)?;
+
+ let mut headers = [httparse::EMPTY_HEADER; 16];
+ let mut http_response = httparse::Response::new(&mut headers);
+ let header_len = match http_response
+ .parse(&response)
+ .map_err(|_| Error::new(AttestationError::IasError))?
+ {
+ httparse::Status::Complete(s) => s,
+ _ => return Err(Error::new(AttestationError::IasError)),
+ };
+
+ let header_map = Self::parse_headers(&http_response);
+
+ if !header_map.contains_key("Content-Length")
+ || header_map
+ .get("Content-Length")
+ .unwrap()
+ .parse::<u32>()
+ .unwrap_or(0)
+ == 0
+ {
+ Ok(Vec::new())
+ } else {
+ let base64 = std::str::from_utf8(&response[header_len..])?;
+
+ let decoded = base64::decode(base64)?;
+ Ok(decoded)
+ }
+ }
+
+ pub fn get_report(&mut self, quote: &[u8]) -> Result<IasReport> {
+ debug!("get_report");
+ let report_uri = "/sgx/dev/attestation/v3/report";
+ let encoded_quote = base64::encode(quote);
+ let encoded_json = format!("{{\"isvEnclaveQuote\":\"{}\"}}\r\n", encoded_quote);
+
+ let request = format!(
+ "POST {} HTTP/1.1\r\n\
+ HOST: {}\r\n\
+ Ocp-Apim-Subscription-Key: {}\r\n\
+ Connection: Close\r\n\
+ Content-Length: {}\r\n\
+ Content-Type: application/json\r\n\r\n\
+ {}",
+ report_uri,
+ self.ias_hostname,
+ self.ias_key,
+ encoded_json.len(),
+ encoded_json
+ );
+ debug!("{}", request);
+
+ let mut stream = self.new_tls_stream()?;
+ stream.write_all(request.as_bytes())?;
+ let mut response = Vec::new();
+ stream.read_to_end(&mut response)?;
+
+ debug!("{}", String::from_utf8_lossy(&response));
+
+ let mut headers = [httparse::EMPTY_HEADER; 16];
+ let mut http_response = httparse::Response::new(&mut headers);
+ debug!("http_response.parse");
+ let header_len = match http_response
+ .parse(&response)
+ .map_err(|_| Error::new(AttestationError::IasError))?
+ {
+ httparse::Status::Complete(s) => s,
+ _ => return Err(Error::new(AttestationError::IasError)),
+ };
+
+ debug!("Self::parse_headers");
+ let header_map = Self::parse_headers(&http_response);
+
+ debug!("get_content_length");
+ if !header_map.contains_key("Content-Length")
+ || header_map
+ .get("Content-Length")
+ .unwrap()
+ .parse::<u32>()
+ .unwrap_or(0)
+ == 0
+ {
+ return Err(Error::new(AttestationError::IasError));
+ }
+
+ debug!("get_signature");
+ let signature = header_map
+ .get("X-IASReport-Signature")
+ .ok_or_else(|| Error::new(AttestationError::IasError))?
+ .to_owned();
+ debug!("get_signing_cert");
+ let signing_cert = {
+ let cert_str = header_map
+ .get("X-IASReport-Signing-Certificate")
+ .ok_or_else(|| Error::new(AttestationError::IasError))?;
+ let decoded_cert = teaclave_utils::percent_decode(cert_str)?;
+ // We should get two concatenated PEM files at this step.
+ let cert_content: Vec<&str> = decoded_cert.split("-----").collect();
+ cert_content[2].to_string()
+ };
+
+ debug!("get_report");
+ let report = String::from_utf8_lossy(&response[header_len..]).into_owned();
+ Ok(IasReport {
+ report,
+ signature,
+ signing_cert,
+ })
+ }
+
+ fn parse_headers(resp: &httparse::Response) -> HashMap<String, String> {
+ let mut header_map = HashMap::new();
+ for h in resp.headers.iter() {
+ header_map.insert(
+ h.name.to_owned(),
+ String::from_utf8_lossy(h.value).into_owned(),
+ );
+ }
+
+ header_map
+ }
+}
diff --git a/teaclave_attestation/src/key.rs b/teaclave_attestation/src/key.rs
new file mode 100644
index 0000000..4171d54
--- /dev/null
+++ b/teaclave_attestation/src/key.rs
@@ -0,0 +1,184 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 anyhow::Result;
+use sgx_tcrypto::SgxEccHandle;
+use sgx_types::{sgx_ec256_private_t, sgx_ec256_public_t};
+use std::prelude::v1::*;
+
+pub const CERT_VALID_DAYS: i64 = 90i64;
+
+pub struct Secp256k1KeyPair {
+ prv_k: sgx_ec256_private_t,
+ pub pub_k: sgx_ec256_public_t,
+}
+
+impl Secp256k1KeyPair {
+ pub fn new() -> Result<Self> {
+ let ecc_handle = SgxEccHandle::new();
+ ecc_handle.open()?;
+ let (prv_k, pub_k) = ecc_handle.create_key_pair()?;
+ ecc_handle.close()?;
+ Ok(Self { prv_k, pub_k })
+ }
+
+ pub fn private_key_into_der(&self) -> Vec<u8> {
+ use bit_vec::BitVec;
+ use yasna::construct_der;
+ use yasna::models::ObjectIdentifier;
+ use yasna::Tag;
+
+ let ec_public_key_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 2, 1]);
+ let prime256v1_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 3, 1, 7]);
+
+ let pub_key_bytes = self.public_key_into_bytes();
+ let prv_key_bytes = self.private_key_into_bytes();
+
+ // Construct private key in DER.
+ construct_der(|writer| {
+ writer.write_sequence(|writer| {
+ writer.next().write_u8(0);
+ writer.next().write_sequence(|writer| {
+ writer.next().write_oid(&ec_public_key_oid);
+ writer.next().write_oid(&prime256v1_oid);
+ });
+ let inner_key_der = construct_der(|writer| {
+ writer.write_sequence(|writer| {
+ writer.next().write_u8(1);
+ writer.next().write_bytes(&prv_key_bytes);
+ writer.next().write_tagged(Tag::context(1), |writer| {
+ writer.write_bitvec(&BitVec::from_bytes(&pub_key_bytes));
+ });
+ });
+ });
+ writer.next().write_bytes(&inner_key_der);
+ });
+ })
+ }
+
+ pub fn create_cert_with_extension(
+ &self,
+ issuer: &str,
+ subject: &str,
+ payload: &[u8],
+ ) -> Vec<u8> {
+ use crate::cert::*;
+ use bit_vec::BitVec;
+ use chrono::TimeZone;
+ use num_bigint::BigUint;
+ use std::time::SystemTime;
+ use std::time::UNIX_EPOCH;
+ use std::untrusted::time::SystemTimeEx;
+ use yasna::construct_der;
+ use yasna::models::{ObjectIdentifier, UTCTime};
+
+ let ecdsa_with_sha256_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 4, 3, 2]);
+ let common_name_oid = ObjectIdentifier::from_slice(&[2, 5, 4, 3]);
+ let ec_public_key_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 2, 1]);
+ let prime256v1_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 3, 1, 7]);
+ let comment_oid = ObjectIdentifier::from_slice(&[2, 16, 840, 1, 113_730, 1, 13]);
+
+ let pub_key_bytes = self.public_key_into_bytes();
+
+ // UNIX_EPOCH is the earliest time stamp. This unwrap should constantly succeed.
+ let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+ let issue_ts = chrono::Utc.timestamp(now.as_secs() as i64, 0);
+
+ // This is guaranteed to be a valid duration.
+ let expire = now + chrono::Duration::days(CERT_VALID_DAYS).to_std().unwrap();
+ let expire_ts = chrono::Utc.timestamp(expire.as_secs() as i64, 0);
+
+ // Construct certificate with payload in extension in DER.
+ let tbs_cert_der = construct_der(|writer| {
+ let version = 2i8;
+ let serial = 1u8;
+ let cert_sign_algo = asn1_seq!(ecdsa_with_sha256_oid.clone());
+ let issuer = asn1_seq!(asn1_seq!(asn1_seq!(
+ common_name_oid.clone(),
+ issuer.to_owned()
+ )));
+ let valid_range = asn1_seq!(
+ UTCTime::from_datetime(&issue_ts),
+ UTCTime::from_datetime(&expire_ts),
+ );
+ let subject = asn1_seq!(asn1_seq!(asn1_seq!(
+ common_name_oid.clone(),
+ subject.to_string(),
+ )));
+ let pub_key = asn1_seq!(
+ asn1_seq!(ec_public_key_oid, prime256v1_oid,),
+ BitVec::from_bytes(&pub_key_bytes),
+ );
+ let sgx_ra_cert_ext = asn1_seq!(asn1_seq!(comment_oid, payload.to_owned()));
+ let tbs_cert = asn1_seq!(
+ version,
+ serial,
+ cert_sign_algo,
+ issuer,
+ valid_range,
+ subject,
+ pub_key,
+ sgx_ra_cert_ext,
+ );
+ TbsCert::dump(writer, tbs_cert);
+ });
+
+ // There will be serious problems if this call fails. We might as well
+ // panic in this case, thus unwrap()
+ let ecc_handle = SgxEccHandle::new();
+ ecc_handle.open().unwrap();
+
+ let sig = ecc_handle
+ .ecdsa_sign_slice(&tbs_cert_der.as_slice(), &self.prv_k)
+ .unwrap();
+
+ let sig_der = yasna::construct_der(|writer| {
+ writer.write_sequence(|writer| {
+ let mut sig_x = sig.x;
+ sig_x.reverse();
+ let mut sig_y = sig.y;
+ sig_y.reverse();
+ writer.next().write_biguint(&BigUint::from_slice(&sig_x));
+ writer.next().write_biguint(&BigUint::from_slice(&sig_y));
+ });
+ });
+
+ yasna::construct_der(|writer| {
+ writer.write_sequence(|writer| {
+ writer.next().write_der(&tbs_cert_der.as_slice());
+ CertSignAlgo::dump(writer.next(), asn1_seq!(ecdsa_with_sha256_oid.clone()));
+ writer
+ .next()
+ .write_bitvec(&BitVec::from_bytes(&sig_der.as_slice()));
+ });
+ })
+ }
+
+ fn public_key_into_bytes(&self) -> Vec<u8> {
+ // The first byte must be 4, which indicates the uncompressed encoding.
+ let mut pub_key_bytes: Vec<u8> = vec![4];
+ pub_key_bytes.extend(self.pub_k.gx.iter().rev());
+ pub_key_bytes.extend(self.pub_k.gy.iter().rev());
+ pub_key_bytes
+ }
+
+ fn private_key_into_bytes(&self) -> Vec<u8> {
+ let mut prv_key_bytes: Vec<u8> = vec![];
+ prv_key_bytes.extend(self.prv_k.r.iter().rev());
+ prv_key_bytes
+ }
+}
diff --git a/teaclave_attestation/src/lib.rs b/teaclave_attestation/src/lib.rs
new file mode 100644
index 0000000..0f2f2b7
--- /dev/null
+++ b/teaclave_attestation/src/lib.rs
@@ -0,0 +1,49 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+#![cfg_attr(feature = "mesalock_sgx", no_std)]
+#[cfg(feature = "mesalock_sgx")]
+#[macro_use]
+extern crate sgx_tstd as std;
+
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum AttestationError {
+ #[error("OCall failed")]
+ OCallError,
+ #[error("Ias error")]
+ IasError,
+ #[error("Get quote error")]
+ QuoteError,
+}
+
+#[macro_use]
+mod cert;
+pub mod quote;
+pub mod verifier;
+
+use cfg_if::cfg_if;
+cfg_if! {
+ if #[cfg(feature = "mesalock_sgx")] {
+ pub mod key;
+ mod report;
+ mod ias;
+ pub use report::IasReport;
+ } else {
+ }
+}
diff --git a/mesatee_core/src/rpc/sgx/auth.rs b/teaclave_attestation/src/quote.rs
similarity index 52%
rename from mesatee_core/src/rpc/sgx/auth.rs
rename to teaclave_attestation/src/quote.rs
index 43ea4b1..ce58552 100644
--- a/mesatee_core/src/rpc/sgx/auth.rs
+++ b/teaclave_attestation/src/quote.rs
@@ -21,6 +21,7 @@
#[cfg(feature = "mesalock_sgx")]
use std::prelude::v1::*;
+use anyhow::{Error, Result};
use chrono::DateTime;
use rustls;
use serde_json;
@@ -34,8 +35,6 @@ use std::untrusted::time::SystemTimeEx;
use uuid::Uuid;
-use teaclave_config::build_config::BUILD_CONFIG;
-
type SignatureAlgorithms = &'static [&'static webpki::SignatureAlgorithm];
static SUPPORTED_SIG_ALGS: SignatureAlgorithms = &[
&webpki::ECDSA_P256_SHA256,
@@ -51,10 +50,15 @@ static SUPPORTED_SIG_ALGS: SignatureAlgorithms = &[
&webpki::RSA_PKCS1_3072_8192_SHA384,
];
-#[derive(Debug)]
+use thiserror::Error;
+
+#[derive(Error, Debug)]
pub enum CertVerificationError {
+ #[error("Invalid cert format")]
InvalidCertFormat,
+ #[error("Bad attestation report")]
BadAttnReport,
+ #[error("Webpki failure")]
WebpkiFailure,
}
@@ -228,136 +232,139 @@ pub struct SgxQuote {
pub body: SgxQuoteBody,
}
-pub(crate) fn extract_sgx_quote_from_mra_cert(
- cert_der: &[u8],
-) -> Result<SgxQuote, CertVerificationError> {
- // Before we reach here, Webpki already verifed the cert is properly signed
- use super::cert::*;
-
- let x509 = yasna::parse_der(cert_der, |reader| X509::load(reader))
- .map_err(|_| CertVerificationError::InvalidCertFormat)?;
-
- let tbs_cert: <TbsCert as Asn1Ty>::ValueTy = x509.0;
-
- let pub_key: <PubKey as Asn1Ty>::ValueTy = ((((((tbs_cert.1).1).1).1).1).1).0;
- let pub_k = (pub_key.1).0;
+impl SgxQuote {
+ pub fn extract_verified_quote(cert_der: &[u8], ias_report_ca_cert: &[u8]) -> Result<SgxQuote> {
+ // Before we reach here, Webpki already verifed the cert is properly signed
+ use super::cert::*;
+
+ let x509 = yasna::parse_der(cert_der, |reader| X509::load(reader))
+ .map_err(|_| CertVerificationError::InvalidCertFormat)?;
+
+ let tbs_cert: <TbsCert as Asn1Ty>::ValueTy = x509.0;
+
+ let pub_key: <PubKey as Asn1Ty>::ValueTy = ((((((tbs_cert.1).1).1).1).1).1).0;
+ let pub_k = (pub_key.1).0;
+
+ let sgx_ra_cert_ext: <SgxRaCertExt as Asn1Ty>::ValueTy =
+ (((((((tbs_cert.1).1).1).1).1).1).1).0;
+
+ let payload: Vec<u8> = ((sgx_ra_cert_ext.0).1).0;
+
+ // Extract each field
+ let mut iter = payload.split(|x| *x == 0x7C);
+ let attn_report_raw = iter
+ .next()
+ .ok_or_else(|| Error::new(CertVerificationError::InvalidCertFormat))?;
+ let sig_raw = iter
+ .next()
+ .ok_or_else(|| Error::new(CertVerificationError::InvalidCertFormat))?;
+ let sig = base64::decode(&sig_raw)?;
+ let sig_cert_raw = iter
+ .next()
+ .ok_or_else(|| Error::new(CertVerificationError::InvalidCertFormat))?;
+ let sig_cert_dec = base64::decode_config(&sig_cert_raw, base64::STANDARD)?;
+
+ let sig_cert = webpki::EndEntityCert::from(&sig_cert_dec)
+ .map_err(|_| CertVerificationError::InvalidCertFormat)?;
+
+ // Verify if the signing cert is issued by Intel CA
+ let mut ias_ca_stripped = ias_report_ca_cert.to_vec();
+ ias_ca_stripped.retain(|&x| x != 0x0d && x != 0x0a);
+ let head_len = "-----BEGIN CERTIFICATE-----".len();
+ let tail_len = "-----END CERTIFICATE-----".len();
+ let full_len = ias_ca_stripped.len();
+ let ias_ca_core: &[u8] = &ias_ca_stripped[head_len..full_len - tail_len];
+ let ias_cert_dec = base64::decode_config(ias_ca_core, base64::STANDARD)
+ .map_err(|_| CertVerificationError::InvalidCertFormat)?;
+
+ let mut ca_reader = BufReader::new(&ias_report_ca_cert[..]);
+
+ let mut root_store = rustls::RootCertStore::empty();
+ root_store
+ .add_pem_file(&mut ca_reader)
+ .expect("Failed to add CA");
+
+ let trust_anchors: Vec<webpki::TrustAnchor> = root_store
+ .roots
+ .iter()
+ .map(|cert| cert.to_trust_anchor())
+ .collect();
+
+ let chain: Vec<&[u8]> = vec![&ias_cert_dec];
+
+ let now_func = webpki::Time::try_from(SystemTime::now())
+ .map_err(|_| CertVerificationError::WebpkiFailure)?;
+
+ sig_cert
+ .verify_is_valid_tls_server_cert(
+ SUPPORTED_SIG_ALGS,
+ &webpki::TLSServerTrustAnchors(&trust_anchors),
+ &chain,
+ now_func,
+ )
+ .map_err(|_| CertVerificationError::WebpkiFailure)?;
+
+ // Verify the signature against the signing cert
+ sig_cert
+ .verify_signature(&webpki::RSA_PKCS1_2048_8192_SHA256, &attn_report_raw, &sig)
+ .map_err(|_| CertVerificationError::WebpkiFailure)?;
+
+ // Verify attestation report
+ let attn_report: Value = serde_json::from_slice(attn_report_raw)
+ .map_err(|_| CertVerificationError::BadAttnReport)?;
+
+ // 1. Check timestamp is within 24H (90day is recommended by Intel)
+ let quote_freshness = {
+ let time = attn_report["timestamp"]
+ .as_str()
+ .ok_or_else(|| Error::new(CertVerificationError::BadAttnReport))?;
+ let time_fixed = String::from(time) + "+0000";
+ let date_time = DateTime::parse_from_str(&time_fixed, "%Y-%m-%dT%H:%M:%S%.f%z")?;
+ let ts = date_time.naive_utc();
+ let now = DateTime::<chrono::offset::Utc>::from(SystemTime::now()).naive_utc();
+ u64::try_from((now - ts).num_seconds())?
+ };
- let sgx_ra_cert_ext: <SgxRaCertExt as Asn1Ty>::ValueTy = (((((((tbs_cert.1).1).1).1).1).1).1).0;
+ // 2. Get quote status
+ let quote_status = {
+ let status_string = attn_report["isvEnclaveQuoteStatus"]
+ .as_str()
+ .ok_or_else(|| Error::new(CertVerificationError::BadAttnReport))?;
- let payload: Vec<u8> = ((sgx_ra_cert_ext.0).1).0;
+ SgxQuoteStatus::from(status_string)
+ };
- use crate::rpc::sgx::fail::MayfailTrace;
+ // 3. Get quote body
+ let quote_body = {
+ let quote_encoded = attn_report["isvEnclaveQuoteBody"]
+ .as_str()
+ .ok_or_else(|| Error::new(CertVerificationError::BadAttnReport))?;
+ let quote_raw = base64::decode(&(quote_encoded.as_bytes()))?;
+ SgxQuoteBody::parse_from(quote_raw.as_slice())
+ .ok_or_else(|| Error::new(CertVerificationError::BadAttnReport))?
+ };
- // Extract each field
- let mut iter = payload.split(|x| *x == 0x7C);
- let (attn_report_raw, sig, sig_cert_dec) = mayfail! {
- attn_report_raw =<< iter.next();
- sig_raw =<< iter.next();
- sig =<< base64::decode(&sig_raw);
- sig_cert_raw =<< iter.next();
- sig_cert_dec =<< base64::decode_config(&sig_cert_raw, base64::STANDARD);
- ret (attn_report_raw, sig, sig_cert_dec)
- }
- .map_err(|_| CertVerificationError::InvalidCertFormat)?;
-
- let sig_cert = webpki::EndEntityCert::from(&sig_cert_dec)
- .map_err(|_| CertVerificationError::InvalidCertFormat)?;
-
- // Verify if the signing cert is issued by Intel CA
- let ias_report_ca = BUILD_CONFIG.ias_root_ca_cert;
- let mut ias_ca_stripped = ias_report_ca.to_vec();
- ias_ca_stripped.retain(|&x| x != 0x0d && x != 0x0a);
- let head_len = "-----BEGIN CERTIFICATE-----".len();
- let tail_len = "-----END CERTIFICATE-----".len();
- let full_len = ias_ca_stripped.len();
- let ias_ca_core: &[u8] = &ias_ca_stripped[head_len..full_len - tail_len];
- let ias_cert_dec = base64::decode_config(ias_ca_core, base64::STANDARD)
- .map_err(|_| CertVerificationError::InvalidCertFormat)?;
-
- let mut ca_reader = BufReader::new(&ias_report_ca[..]);
-
- let mut root_store = rustls::RootCertStore::empty();
- root_store
- .add_pem_file(&mut ca_reader)
- .expect("Failed to add CA");
-
- let trust_anchors: Vec<webpki::TrustAnchor> = root_store
- .roots
- .iter()
- .map(|cert| cert.to_trust_anchor())
- .collect();
-
- let chain: Vec<&[u8]> = vec![&ias_cert_dec];
-
- let now_func = webpki::Time::try_from(SystemTime::now())
- .map_err(|_| CertVerificationError::WebpkiFailure)?;
-
- sig_cert
- .verify_is_valid_tls_server_cert(
- SUPPORTED_SIG_ALGS,
- &webpki::TLSServerTrustAnchors(&trust_anchors),
- &chain,
- now_func,
- )
- .map_err(|_| CertVerificationError::WebpkiFailure)?;
-
- // Verify the signature against the signing cert
- sig_cert
- .verify_signature(&webpki::RSA_PKCS1_2048_8192_SHA256, &attn_report_raw, &sig)
- .map_err(|_| CertVerificationError::WebpkiFailure)?;
-
- // Verify attestation report
- let attn_report: Value = serde_json::from_slice(attn_report_raw)
- .map_err(|_| CertVerificationError::BadAttnReport)?;
-
- // 1. Check timestamp is within 24H (90day is recommended by Intel)
- let quote_freshness = mayfail! {
- time =<< attn_report["timestamp"].as_str();
- let time_fixed = String::from(time) + "+0000";
- date_time =<< DateTime::parse_from_str(&time_fixed, "%Y-%m-%dT%H:%M:%S%.f%z");
- let ts = date_time.naive_utc();
- let now = DateTime::<chrono::offset::Utc>::from(SystemTime::now()).naive_utc();
- secs =<< u64::try_from((now - ts).num_seconds());
- ret secs
- }
- .map_err(|_| CertVerificationError::BadAttnReport)?;
+ let raw_pub_k = pub_k.to_bytes();
+
+ // According to RFC 5480 `Elliptic Curve Cryptography Subject Public Key Information',
+ // SEC 2.2:
+ // ``The first octet of the OCTET STRING indicates whether the key is
+ // compressed or uncompressed. The uncompressed form is indicated
+ // by 0x04 and the compressed form is indicated by either 0x02 or
+ // 0x03 (see 2.3.3 in [SEC1]). The public key MUST be rejected if
+ // any other value is included in the first octet.''
+ //
+ // We only accept the uncompressed form here.
+ let is_uncompressed = raw_pub_k[0] == 4;
+ let pub_k = &raw_pub_k.as_slice()[1..];
+ if !is_uncompressed || pub_k != "e_body.report_body.report_data[..] {
+ return Err(Error::new(CertVerificationError::BadAttnReport));
+ }
- // 2. Get quote status
- let quote_status = mayfail! {
- status_string =<< attn_report["isvEnclaveQuoteStatus"].as_str();
- ret SgxQuoteStatus::from(status_string)
- }
- .map_err(|_| CertVerificationError::BadAttnReport)?;
-
- // 3. Get quote body
- let quote_body = mayfail! {
- quote_encoded =<< attn_report["isvEnclaveQuoteBody"].as_str();
- quote_raw =<< base64::decode(&(quote_encoded.as_bytes()));
- quote_body =<< SgxQuoteBody::parse_from(quote_raw.as_slice());
- ret quote_body
- }
- .map_err(|_| CertVerificationError::BadAttnReport)?;
-
- let raw_pub_k = pub_k.to_bytes();
-
- // According to RFC 5480 `Elliptic Curve Cryptography Subject Public Key Information',
- // SEC 2.2:
- // ``The first octet of the OCTET STRING indicates whether the key is
- // compressed or uncompressed. The uncompressed form is indicated
- // by 0x04 and the compressed form is indicated by either 0x02 or
- // 0x03 (see 2.3.3 in [SEC1]). The public key MUST be rejected if
- // any other value is included in the first octet.''
- //
- // In the case of MesaTEE, we only allow the uncompressed form.
- let is_uncompressed = raw_pub_k[0] == 4;
- let pub_k = &raw_pub_k.as_slice()[1..];
- if !is_uncompressed || pub_k != "e_body.report_body.report_data[..] {
- return Err(CertVerificationError::BadAttnReport);
+ Ok(SgxQuote {
+ freshness: std::time::Duration::from_secs(quote_freshness),
+ status: quote_status,
+ body: quote_body,
+ })
}
-
- Ok(SgxQuote {
- freshness: std::time::Duration::from_secs(quote_freshness),
- status: quote_status,
- body: quote_body,
- })
}
diff --git a/teaclave_attestation/src/report.rs b/teaclave_attestation/src/report.rs
new file mode 100644
index 0000000..a4a2689
--- /dev/null
+++ b/teaclave_attestation/src/report.rs
@@ -0,0 +1,199 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 crate::ias::IasClient;
+use crate::AttestationError;
+use anyhow::Error;
+use anyhow::Result;
+use log::debug;
+use sgx_rand::os::SgxRng;
+use sgx_rand::Rng;
+use sgx_tcrypto::rsgx_sha256_slice;
+use sgx_tse::{rsgx_create_report, rsgx_verify_report};
+use sgx_types::sgx_ec256_public_t;
+use sgx_types::*;
+use std::prelude::v1::*;
+use teaclave_utils;
+
+extern "C" {
+ fn ocall_sgx_init_quote(
+ p_retval: *mut sgx_status_t,
+ p_target_info: *mut sgx_target_info_t,
+ p_gid: *mut sgx_epid_group_id_t,
+ ) -> sgx_status_t;
+
+ fn ocall_sgx_calc_quote_size(
+ p_retval: *mut sgx_status_t,
+ p_sig_rl: *const u8,
+ sig_rl_size: u32,
+ p_quote_size: *mut u32,
+ ) -> sgx_status_t;
+
+ fn ocall_sgx_get_quote(
+ p_retval: *mut sgx_status_t,
+ p_report: *const sgx_report_t,
+ quote_type: sgx_quote_sign_type_t,
+ p_spid: *const sgx_spid_t,
+ p_nonce: *const sgx_quote_nonce_t,
+ p_sig_rl: *const u8,
+ sig_rl_size: u32,
+ p_qe_report: *mut sgx_report_t,
+ p_quote: *mut u8,
+ quote_size: u32,
+ ) -> sgx_status_t;
+}
+
+#[derive(Default)]
+pub struct IasReport {
+ pub report: String,
+ pub signature: String,
+ pub signing_cert: String,
+}
+
+impl IasReport {
+ pub fn new(
+ pub_k: sgx_ec256_public_t,
+ ias_key: &str,
+ ias_spid: &str,
+ production: bool,
+ ) -> Result<Self> {
+ let (target_info, epid_group_id) = Self::init_quote()?;
+ let mut ias_client = IasClient::new(ias_key, production);
+ let sigrl = ias_client.get_sigrl(u32::from_le_bytes(epid_group_id))?;
+ let report = Self::create_report(pub_k, target_info)?;
+ let quote = Self::get_quote(&sigrl, report, target_info, ias_spid)?;
+ let report = ias_client.get_report("e)?;
+ Ok(report)
+ }
+
+ fn init_quote() -> Result<(sgx_target_info_t, sgx_epid_group_id_t)> {
+ debug!("init_quote");
+ let mut ti: sgx_target_info_t = sgx_target_info_t::default();
+ let mut eg: sgx_epid_group_id_t = sgx_epid_group_id_t::default();
+ let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED;
+
+ let res = unsafe { ocall_sgx_init_quote(&mut rt as _, &mut ti as _, &mut eg as _) };
+
+ if res != sgx_status_t::SGX_SUCCESS || rt != sgx_status_t::SGX_SUCCESS {
+ Err(Error::new(AttestationError::OCallError))
+ } else {
+ Ok((ti, eg))
+ }
+ }
+
+ fn create_report(
+ pub_k: sgx_ec256_public_t,
+ target_info: sgx_target_info_t,
+ ) -> Result<sgx_report_t> {
+ debug!("create_report");
+ let mut report_data: sgx_report_data_t = sgx_report_data_t::default();
+ let mut pub_k_gx = pub_k.gx;
+ pub_k_gx.reverse();
+ let mut pub_k_gy = pub_k.gy;
+ pub_k_gy.reverse();
+ report_data.d[..32].clone_from_slice(&pub_k_gx);
+ report_data.d[32..].clone_from_slice(&pub_k_gy);
+
+ rsgx_create_report(&target_info, &report_data)
+ .map_err(|_| Error::new(AttestationError::IasError))
+ }
+
+ fn get_quote(
+ sigrl: &[u8],
+ report: sgx_report_t,
+ target_info: sgx_target_info_t,
+ ias_spid: &str,
+ ) -> Result<Vec<u8>> {
+ let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED;
+ let (p_sigrl, sigrl_len) = if sigrl.is_empty() {
+ (std::ptr::null(), 0)
+ } else {
+ (sigrl.as_ptr(), sigrl.len() as u32)
+ };
+ let mut quote_len: u32 = 0;
+
+ let res = unsafe {
+ ocall_sgx_calc_quote_size(&mut rt as _, p_sigrl, sigrl_len, &mut quote_len as _)
+ };
+
+ if res != sgx_status_t::SGX_SUCCESS || rt != sgx_status_t::SGX_SUCCESS {
+ return Err(Error::new(AttestationError::OCallError));
+ }
+
+ let mut quote_nonce = sgx_quote_nonce_t { rand: [0; 16] };
+ let mut os_rng = SgxRng::new()?;
+ os_rng.fill_bytes(&mut quote_nonce.rand);
+ let mut qe_report = sgx_report_t::default();
+
+ let quote_type = sgx_quote_sign_type_t::SGX_LINKABLE_SIGNATURE;
+ let spid: sgx_spid_t = teaclave_utils::decode_spid(ias_spid)?;
+
+ let mut quote = vec![0; quote_len as usize];
+
+ debug!("ocall_sgx_get_quote");
+ let res = unsafe {
+ ocall_sgx_get_quote(
+ &mut rt as _,
+ &report as _,
+ quote_type,
+ &spid as _,
+ "e_nonce as _,
+ p_sigrl,
+ sigrl_len,
+ &mut qe_report as _,
+ quote.as_mut_ptr(),
+ quote_len,
+ )
+ };
+
+ if res != sgx_status_t::SGX_SUCCESS || rt != sgx_status_t::SGX_SUCCESS {
+ return Err(Error::new(AttestationError::OCallError));
+ }
+
+ debug!("rsgx_verify_report");
+ // Perform a check on qe_report to verify if the qe_report is valid.
+ rsgx_verify_report(&qe_report).map_err(|_| Error::new(AttestationError::IasError))?;
+
+ // Check if the qe_report is produced on the same platform.
+ if target_info.mr_enclave.m != qe_report.body.mr_enclave.m
+ || target_info.attributes.flags != qe_report.body.attributes.flags
+ || target_info.attributes.xfrm != qe_report.body.attributes.xfrm
+ {
+ return Err(Error::new(AttestationError::QuoteError));
+ }
+
+ // Check qe_report to defend against replay attack. The purpose of
+ // p_qe_report is for the ISV enclave to confirm the QUOTE it received
+ // is not modified by the untrusted SW stack, and not a replay. The
+ // implementation in QE is to generate a REPORT targeting the ISV
+ // enclave (target info from p_report) , with the lower 32Bytes in
+ // report.data = SHA256(p_nonce||p_quote). The ISV enclave can verify
+ // the p_qe_report and report.data to confirm the QUOTE has not be
+ // modified and is not a replay. It is optional.
+ let mut rhs_vec: Vec<u8> = quote_nonce.rand.to_vec();
+ rhs_vec.extend("e);
+ debug!("rsgx_sha256_slice");
+ let rhs_hash =
+ rsgx_sha256_slice(&rhs_vec).map_err(|_| Error::new(AttestationError::IasError))?;
+ let lhs_hash = &qe_report.body.report_data.d[..32];
+ if rhs_hash != lhs_hash {
+ return Err(Error::new(AttestationError::QuoteError));
+ }
+
+ Ok(quote)
+ }
+}
diff --git a/teaclave_attestation/src/verifier.rs b/teaclave_attestation/src/verifier.rs
new file mode 100644
index 0000000..591dfa4
--- /dev/null
+++ b/teaclave_attestation/src/verifier.rs
@@ -0,0 +1,153 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 crate::quote::SgxQuote;
+use std::hash::{Hash, Hasher};
+use std::vec::Vec;
+use teaclave_config::build_config::BUILD_CONFIG;
+use teaclave_utils::EnclaveMeasurement;
+
+#[derive(Clone)]
+pub struct EnclaveAttr {
+ pub measures: Vec<EnclaveMeasurement>,
+}
+
+impl PartialEq for EnclaveAttr {
+ fn eq(&self, other: &EnclaveAttr) -> bool {
+ self.measures == other.measures
+ }
+}
+
+impl Hash for EnclaveAttr {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ for m in &self.measures {
+ m.mr_enclave.hash(state);
+ m.mr_signer.hash(state);
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct SgxQuoteVerifier {
+ pub enclave_attr: EnclaveAttr,
+ pub verifier: fn(&SgxQuote) -> bool,
+}
+
+impl PartialEq for SgxQuoteVerifier {
+ fn eq(&self, other: &SgxQuoteVerifier) -> bool {
+ self.verifier as usize == other.verifier as usize && self.enclave_attr == other.enclave_attr
+ }
+}
+
+impl Eq for SgxQuoteVerifier {}
+
+impl Hash for SgxQuoteVerifier {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.enclave_attr.hash(state);
+ (self.verifier as usize).hash(state);
+ }
+}
+
+fn universal_quote_verifier(quote: &SgxQuote) -> bool {
+ quote.status != crate::quote::SgxQuoteStatus::UnknownBadStatus
+}
+
+impl SgxQuoteVerifier {
+ pub fn new(enclave_attr: EnclaveAttr) -> Self {
+ Self {
+ enclave_attr,
+ verifier: universal_quote_verifier,
+ }
+ }
+
+ fn verify_measures(&self, quote: &SgxQuote) -> bool {
+ let this_mr_signer = quote.body.report_body.mr_signer;
+ let this_mr_enclave = quote.body.report_body.mr_enclave;
+
+ self.enclave_attr
+ .measures
+ .iter()
+ .any(|m| m.mr_signer == this_mr_signer && m.mr_enclave == this_mr_enclave)
+ }
+
+ fn verify_cert(&self, cert_der: &[u8]) -> bool {
+ if cfg!(sgx_sim) {
+ return true;
+ }
+
+ let quote = match SgxQuote::extract_verified_quote(&cert_der, BUILD_CONFIG.ias_root_ca_cert)
+ {
+ Ok(quote) => quote,
+ Err(_) => {
+ return false;
+ }
+ };
+
+ // Enclave measures are not tested in test mode since we have
+ // a dedicated test enclave not known to production enclaves
+ if cfg!(test_mode) {
+ return (self.verifier)("e);
+ }
+
+ self.verify_measures("e) && (self.verifier)("e)
+ }
+}
+
+impl rustls::ServerCertVerifier for SgxQuoteVerifier {
+ fn verify_server_cert(
+ &self,
+ _roots: &rustls::RootCertStore,
+ certs: &[rustls::Certificate],
+ _hostname: webpki::DNSNameRef,
+ _ocsp: &[u8],
+ ) -> std::result::Result<rustls::ServerCertVerified, rustls::TLSError> {
+ // This call automatically verifies certificate signature
+ if certs.len() != 1 {
+ return Err(rustls::TLSError::NoCertificatesPresented);
+ }
+ if self.verify_cert(&certs[0].0) {
+ Ok(rustls::ServerCertVerified::assertion())
+ } else {
+ Err(rustls::TLSError::WebPKIError(
+ webpki::Error::ExtensionValueInvalid,
+ ))
+ }
+ }
+}
+
+impl rustls::ClientCertVerifier for SgxQuoteVerifier {
+ fn client_auth_root_subjects(&self) -> rustls::DistinguishedNames {
+ rustls::DistinguishedNames::new()
+ }
+
+ fn verify_client_cert(
+ &self,
+ certs: &[rustls::Certificate],
+ ) -> std::result::Result<rustls::ClientCertVerified, rustls::TLSError> {
+ // This call automatically verifies certificate signature
+ if certs.len() != 1 {
+ return Err(rustls::TLSError::NoCertificatesPresented);
+ }
+ if self.verify_cert(&certs[0].0) {
+ Ok(rustls::ClientCertVerified::assertion())
+ } else {
+ Err(rustls::TLSError::WebPKIError(
+ webpki::Error::ExtensionValueInvalid,
+ ))
+ }
+ }
+}
diff --git a/teaclave_common/mayfail/Cargo.toml b/teaclave_common/mayfail/Cargo.toml
new file mode 100644
index 0000000..27d2eaa
--- /dev/null
+++ b/teaclave_common/mayfail/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "mayfail"
+version = "0.1.0"
+authors = ["MesaTEE Authors <de...@mesatee.org>"]
+license = "Apache-2.0"
+edition = "2018"
+
+[features]
+default = []
+mesalock_sgx = ["sgx_tstd", "mesatee_core/mesalock_sgx"]
+sgx_tstd = { version = "1.1.0", features = ["net", "backtrace"], optional = true }
+
+[dependencies]
+mesatee_core = { version = "0.1.0" }
diff --git a/mesatee_core/src/rpc/sgx/fail.rs b/teaclave_common/mayfail/src/lib.rs
similarity index 96%
rename from mesatee_core/src/rpc/sgx/fail.rs
rename to teaclave_common/mayfail/src/lib.rs
index a99cfa5..d0bed80 100644
--- a/mesatee_core/src/rpc/sgx/fail.rs
+++ b/teaclave_common/mayfail/src/lib.rs
@@ -17,6 +17,10 @@
//! Monadic mayfail notation for chained error handling
+#![cfg_attr(feature = "mesalock_sgx", no_std)]
+#[cfg(feature = "mesalock_sgx")]
+extern crate sgx_tstd as std;
+
/// maiyfail use duck typing.
///
/// Syntax:
@@ -54,6 +58,7 @@
/// assert!(ret.is_ok() && ret.unwrap() == 4);
/// }
/// ```
+
macro_rules! mayfail {
(let $p: pat = $e: expr ; $($t: tt)*) => (
{ let $p = $e; mayfail! { $($t)* } }
@@ -70,9 +75,9 @@ macro_rules! mayfail {
(ret $f: expr) => (Ok($f))
}
-use crate::Error;
-use crate::ErrorKind;
-use crate::Result;
+use mesatee_core::Error;
+use mesatee_core::ErrorKind;
+use mesatee_core::Result;
pub trait MayfailNop<T> {
fn to_mt_result(self: Self, file: &'static str, line: u32) -> Result<T>;
diff --git a/teaclave_utils/src/lib.rs b/teaclave_utils/src/lib.rs
index 4220309..a7f774c 100644
--- a/teaclave_utils/src/lib.rs
+++ b/teaclave_utils/src/lib.rs
@@ -8,15 +8,31 @@ use serde::Deserializer;
use serde_derive::Deserialize;
use std::collections::HashMap;
+use std::error::Error;
+use std::fmt;
+
type Result<T> = std::result::Result<T, UtilsError>;
use sgx_types::SGX_HASH_SIZE;
pub type SgxMeasurement = [u8; SGX_HASH_SIZE];
+#[derive(Debug)]
pub enum UtilsError {
ParseError,
}
+impl fmt::Display for UtilsError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Teaclave utils error")
+ }
+}
+
+impl Error for UtilsError {
+ fn description(&self) -> &str {
+ "Teaclave utils error"
+ }
+}
+
fn decode_hex_digit(digit: char) -> Result<u8> {
match digit {
'0'..='9' => Ok(digit as u8 - b'0'),
@@ -58,7 +74,8 @@ pub fn decode_spid(hex: &str) -> Result<sgx_types::sgx_spid_t> {
Ok(spid)
}
-pub fn percent_decode(orig: String) -> Result<String> {
+pub fn percent_decode(orig: &str) -> Result<String> {
+ let orig = orig.replace("%0A", "");
let v: Vec<&str> = orig.split('%').collect();
let mut ret = String::new();
ret.push_str(v[0]);
diff --git a/third_party/crates-io b/third_party/crates-io
index 8b4b4e6..80cc441 160000
--- a/third_party/crates-io
+++ b/third_party/crates-io
@@ -1 +1 @@
-Subproject commit 8b4b4e6bbb0a6e7bbcd764e62d2c540472f57547
+Subproject commit 80cc441d07d8f89f9f57fe32a72d49e418d2705e
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@teaclave.apache.org
For additional commands, e-mail: commits-help@teaclave.apache.org