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)(&quote);
-        }
-
-        let this_mr_signer = &quote.body.report_body.mr_signer;
-        let this_mr_enclave = &quote.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)(&quote)
-    }
-}
-
-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 = &quote_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(&quote);
-    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, &quote)
-}
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 != &quote_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 != &quote_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(&quote)?;
+        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 _,
+                &quote_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(&quote);
+        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)(&quote);
+        }
+
+        self.verify_measures(&quote) && (self.verifier)(&quote)
+    }
+}
+
+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