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 2020/01/17 02:01:45 UTC
[incubator-teaclave] 01/02: [attestation] Polishing remote
attestation and TLS-RA
This is an automated email from the ASF dual-hosted git repository.
mssun pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-teaclave.git
commit 5615c7136d675ef126eef5d70e66e69e4d077f67
Author: Mingshen Sun <bo...@mssun.me>
AuthorDate: Thu Jan 16 16:30:16 2020 -0800
[attestation] Polishing remote attestation and TLS-RA
---
attestation/Cargo.toml | 1 +
attestation/src/attestation.rs | 11 +-
attestation/src/ias.rs | 110 +++--
attestation/src/lib.rs | 8 +-
attestation/src/platform.rs | 159 ++++++++
attestation/src/quote.rs | 347 ----------------
attestation/src/report.rs | 452 ++++++++++++++-------
attestation/src/verifier.rs | 48 ++-
rpc/Cargo.toml | 1 +
rpc/src/config.rs | 15 +
.../service_functional_test/enclave/src/lib.rs | 1 +
.../enclave/src/teaclave_authentication_service.rs | 20 +-
12 files changed, 600 insertions(+), 573 deletions(-)
diff --git a/attestation/Cargo.toml b/attestation/Cargo.toml
index 28c1c15..6b2f965 100644
--- a/attestation/Cargo.toml
+++ b/attestation/Cargo.toml
@@ -27,6 +27,7 @@ log = { version = "0.4.6" }
num-bigint = { version = "0.2.2" }
percent-encoding = { version = "2.1.0" }
rustls = { version = "0.16.0", features = ["dangerous_configuration"] }
+serde = { version = "1.0.92", features = ["derive"] }
serde_json = { version = "1.0.39" }
thiserror = { version = "1.0.9" }
uuid = { version = "0.7.4", features = ["v4"] }
diff --git a/attestation/src/attestation.rs b/attestation/src/attestation.rs
index 4a823c6..92d5e06 100644
--- a/attestation/src/attestation.rs
+++ b/attestation/src/attestation.rs
@@ -23,14 +23,11 @@ impl RemoteAttestation {
IasReport::new(key_pair.pub_k, ias_key, ias_spid, false)?
};
- let cert_extension_payload =
- [report.report, report.signature, report.signing_cert].join("|");
+ let cert_extension = serde_json::to_vec(&report)?;
- let cert_der = key_pair.create_cert_with_extension(
- "Teaclave",
- "Teaclave",
- &cert_extension_payload.as_bytes(),
- );
+ let issuer = "Teaclave";
+ let subject = "CN=Teaclave";
+ let cert_der = key_pair.create_cert_with_extension(issuer, subject, &cert_extension);
let prv_key_der = key_pair.private_key_into_der();
let time = SystemTime::now();
diff --git a/attestation/src/ias.rs b/attestation/src/ias.rs
index 022b54d..9af7186 100644
--- a/attestation/src/ias.rs
+++ b/attestation/src/ias.rs
@@ -15,23 +15,52 @@
// specific language governing permissions and limitations
// under the License.
-use crate::report::IasReport;
use crate::AttestationError;
use anyhow::Error;
use anyhow::Result;
+use anyhow::{anyhow, bail};
use log::{debug, trace};
use percent_encoding;
+use serde::{Deserialize, Serialize};
use sgx_types::*;
use std::collections::HashMap;
use std::io::{Read, Write};
use std::net::TcpStream;
+use std::os::unix::io::FromRawFd;
use std::prelude::v1::*;
use std::sync::Arc;
+#[cfg(feature = "mesalock_sgx")]
extern "C" {
fn ocall_sgx_get_ias_socket(p_retval: *mut i32) -> sgx_status_t;
}
+#[derive(Default, Serialize, Deserialize)]
+pub struct IasReport {
+ pub report: Vec<u8>,
+ pub signature: Vec<u8>,
+ pub signing_cert: Vec<u8>,
+}
+
+impl IasReport {
+ #[cfg(feature = "mesalock_sgx")]
+ pub fn new(
+ pub_k: sgx_types::sgx_ec256_public_t,
+ ias_key: &str,
+ ias_spid: &str,
+ production: bool,
+ ) -> anyhow::Result<Self> {
+ use crate::platform;
+ let (target_info, epid_group_id) = platform::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 sgx_report = platform::create_report(pub_k, target_info)?;
+ let quote = platform::get_quote(&sigrl, sgx_report, target_info, ias_spid)?;
+ let ias_report = ias_client.get_report("e)?;
+ Ok(ias_report)
+ }
+}
+
pub struct IasClient {
ias_key: String,
ias_hostname: &'static str,
@@ -51,27 +80,15 @@ impl IasClient {
}
}
- 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 fd = get_ias_socket()?;
+ let socket = unsafe { TcpStream::from_raw_fd(fd) };
let stream = rustls::StreamOwned::new(client, socket);
Ok(stream)
@@ -99,10 +116,10 @@ impl IasClient {
.map_err(|_| Error::new(AttestationError::IasError))?
{
httparse::Status::Complete(s) => s,
- _ => return Err(Error::new(AttestationError::IasError)),
+ _ => bail!(Error::new(AttestationError::IasError)),
};
- let header_map = Self::parse_headers(&http_response);
+ let header_map = parse_headers(&http_response);
if !header_map.contains_key("Content-Length")
|| header_map
@@ -158,11 +175,10 @@ impl IasClient {
.map_err(|_| Error::new(AttestationError::IasError))?
{
httparse::Status::Complete(s) => s,
- _ => return Err(Error::new(AttestationError::IasError)),
+ _ => bail!(AttestationError::IasError),
};
- debug!("Self::parse_headers");
- let header_map = Self::parse_headers(&http_response);
+ let header_map = parse_headers(&http_response);
debug!("get_content_length");
if !header_map.contains_key("Content-Length")
@@ -173,42 +189,64 @@ impl IasClient {
.unwrap_or(0)
== 0
{
- return Err(Error::new(AttestationError::IasError));
+ bail!(Error::new(AttestationError::IasError));
}
debug!("get_signature");
let signature = header_map
.get("X-IASReport-Signature")
- .ok_or_else(|| Error::new(AttestationError::IasError))?
- .to_owned();
+ .ok_or_else(|| Error::new(AttestationError::IasError))?;
+ let signature = base64::decode(signature)?;
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 = percent_encoding::percent_decode_str(cert_str).decode_utf8()?;
- // We should get two concatenated PEM files at this step.
- let cert_content: Vec<&str> = decoded_cert.split("-----").collect();
- cert_content[2].to_string()
+ let certs = rustls::internal::pemfile::certs(&mut decoded_cert.as_bytes())
+ .map_err(|_| anyhow!("pemfile error"))?;
+ certs[0].0.clone()
};
- let report = String::from_utf8_lossy(&response[header_len..]).into_owned();
+ let report = response[header_len..].to_vec();
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(),
- );
- }
+fn parse_headers(resp: &httparse::Response) -> HashMap<String, String> {
+ debug!("parse_headers");
+ 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
+ header_map
+}
+
+#[cfg(feature = "mesalock_sgx")]
+fn get_ias_socket() -> Result<c_int> {
+ debug!("get_ias_socket");
+ let mut fd: c_int = -1;
+ let res = unsafe { ocall_sgx_get_ias_socket(&mut fd as _) };
+
+ if res != sgx_status_t::SGX_SUCCESS || fd < 0 {
+ bail!(AttestationError::OCallError)
+ } else {
+ Ok(fd)
}
}
+
+#[cfg(not(feature = "mesalock_sgx"))]
+fn get_ias_socket() -> Result<c_int> {
+ use std::os::unix::io::IntoRawFd;
+ let ias_addr = "api.trustedservices.intel.com:443";
+ let stream = TcpStream::connect(ias_addr)?;
+ Ok(stream.into_raw_fd())
+}
diff --git a/attestation/src/lib.rs b/attestation/src/lib.rs
index 036f46f..81dd8eb 100644
--- a/attestation/src/lib.rs
+++ b/attestation/src/lib.rs
@@ -34,17 +34,17 @@ pub enum AttestationError {
#[macro_use]
mod cert;
-pub mod quote;
+pub mod ias;
+pub mod report;
pub mod verifier;
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "mesalock_sgx")] {
pub mod key;
- mod report;
- mod ias;
+ mod platform;
mod attestation;
- pub use report::IasReport;
+ pub use ias::IasReport;
pub use attestation::RemoteAttestation;
} else {
}
diff --git a/attestation/src/platform.rs b/attestation/src/platform.rs
new file mode 100644
index 0000000..fda919a
--- /dev/null
+++ b/attestation/src/platform.rs
@@ -0,0 +1,159 @@
+use crate::AttestationError;
+use anyhow::bail;
+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::*;
+
+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;
+}
+
+pub(crate) 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 {
+ bail!(AttestationError::OCallError)
+ } else {
+ Ok((ti, eg))
+ }
+}
+
+pub(crate) 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))
+}
+
+pub(crate) fn get_quote(
+ sigrl: &[u8],
+ report: sgx_report_t,
+ target_info: sgx_target_info_t,
+ ias_spid_str: &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 {
+ bail!(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 mut spid = sgx_types::sgx_spid_t::default();
+ let hex = hex::decode(ias_spid_str)?;
+ spid.id.copy_from_slice(&hex[..16]);
+
+ 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 {
+ bail!(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
+ {
+ bail!(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 {
+ bail!(AttestationError::QuoteError);
+ }
+
+ Ok(quote)
+}
diff --git a/attestation/src/quote.rs b/attestation/src/quote.rs
deleted file mode 100644
index a389010..0000000
--- a/attestation/src/quote.rs
+++ /dev/null
@@ -1,347 +0,0 @@
-// 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.
-
-#![allow(clippy::redundant_closure)]
-
-// Insert std prelude in the top for the sgx feature
-#[cfg(feature = "mesalock_sgx")]
-use std::prelude::v1::*;
-
-use anyhow::anyhow;
-use anyhow::{Error, Result};
-use chrono::DateTime;
-use rustls;
-use serde_json;
-use serde_json::Value;
-use std::convert::TryFrom;
-use std::time::*;
-
-#[cfg(feature = "mesalock_sgx")]
-use std::untrusted::time::SystemTimeEx;
-
-use uuid::Uuid;
-
-type SignatureAlgorithms = &'static [&'static webpki::SignatureAlgorithm];
-static SUPPORTED_SIG_ALGS: SignatureAlgorithms = &[
- &webpki::ECDSA_P256_SHA256,
- &webpki::ECDSA_P256_SHA384,
- &webpki::ECDSA_P384_SHA256,
- &webpki::ECDSA_P384_SHA384,
- &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
- &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
- &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
- &webpki::RSA_PKCS1_2048_8192_SHA256,
- &webpki::RSA_PKCS1_2048_8192_SHA384,
- &webpki::RSA_PKCS1_2048_8192_SHA512,
- &webpki::RSA_PKCS1_3072_8192_SHA384,
-];
-
-use thiserror::Error;
-
-#[derive(Error, Debug)]
-pub enum QuoteParsingError {
- #[error("Invalid cert format")]
- InvalidCertFormat,
- #[error("Bad attestation report")]
- BadAttnReport,
-}
-
-pub struct SgxReport {
- pub cpu_svn: [u8; 16],
- pub misc_select: u32,
- pub attributes: [u8; 16],
- pub mr_enclave: [u8; 32],
- pub mr_signer: [u8; 32],
- pub isv_prod_id: u16,
- pub isv_svn: u16,
- pub report_data: [u8; 64],
-}
-
-pub enum SgxQuoteVersion {
- V1,
- V2,
-}
-
-pub enum SgxQuoteSigType {
- Unlinkable,
- Linkable,
-}
-
-#[derive(PartialEq, Debug)]
-pub enum SgxQuoteStatus {
- OK,
- GroupOutOfDate,
- ConfigurationNeeded,
- UnknownBadStatus,
-}
-
-impl From<&str> for SgxQuoteStatus {
- fn from(status: &str) -> Self {
- match status {
- "OK" => SgxQuoteStatus::OK,
- "GROUP_OUT_OF_DATE" => SgxQuoteStatus::GroupOutOfDate,
- "CONFIGURATION_NEEDED" => SgxQuoteStatus::ConfigurationNeeded,
- _ => SgxQuoteStatus::UnknownBadStatus,
- }
- }
-}
-
-pub struct SgxQuoteBody {
- pub version: SgxQuoteVersion,
- pub signature_type: SgxQuoteSigType,
- pub gid: u32,
- pub isv_svn_qe: u16,
- pub isv_svn_pce: u16,
- pub qe_vendor_id: Uuid,
- pub user_data: [u8; 20],
- pub report_body: SgxReport,
-}
-
-impl SgxQuoteBody {
- fn parse_from<'a>(bytes: &'a [u8]) -> Result<Self> {
- let mut pos: usize = 0;
- // TODO: It is really unnecessary to construct a Vec<u8> each time.
- // Try to optimize this.
- let mut take = |n: usize| -> Result<&'a [u8]> {
- if n > 0 && bytes.len() >= pos + n {
- let ret = &bytes[pos..pos + n];
- pos += n;
- Ok(ret)
- } else {
- Err(anyhow!("Quote parsing error."))
- }
- };
-
- // off 0, size 2
- let version = match u16::from_le_bytes(<[u8; 2]>::try_from(take(2)?)?) {
- 1 => SgxQuoteVersion::V1,
- 2 => SgxQuoteVersion::V2,
- _ => return Err(anyhow!("Quote parsing error.")),
- };
-
- // off 2, size 2
- let signature_type = match u16::from_le_bytes(<[u8; 2]>::try_from(take(2)?)?) {
- 0 => SgxQuoteSigType::Unlinkable,
- 1 => SgxQuoteSigType::Linkable,
- _ => return Err(anyhow!("Quote parsing error.")),
- };
-
- // off 4, size 4
- let gid = u32::from_le_bytes(<[u8; 4]>::try_from(take(4)?)?);
-
- // off 8, size 2
- let isv_svn_qe = u16::from_le_bytes(<[u8; 2]>::try_from(take(2)?)?);
-
- // off 10, size 2
- let isv_svn_pce = u16::from_le_bytes(<[u8; 2]>::try_from(take(2)?)?);
-
- // off 12, size 16
- let qe_vendor_id_raw = <[u8; 16]>::try_from(take(16)?)?;
- let qe_vendor_id = Uuid::from_slice(&qe_vendor_id_raw)?;
-
- // off 28, size 20
- let user_data = <[u8; 20]>::try_from(take(20)?)?;
-
- // off 48, size 16
- let cpu_svn = <[u8; 16]>::try_from(take(16)?)?;
-
- // off 64, size 4
- let misc_select = u32::from_le_bytes(<[u8; 4]>::try_from(take(4)?)?);
-
- // off 68, size 28
- let _reserved = take(28)?;
-
- // off 96, size 16
- let attributes = <[u8; 16]>::try_from(take(16)?)?;
-
- // off 112, size 32
- let mr_enclave = <[u8; 32]>::try_from(take(32)?)?;
-
- // off 144, size 32
- let _reserved = take(32)?;
-
- // off 176, size 32
- let mr_signer = <[u8; 32]>::try_from(take(32)?)?;
-
- // off 208, size 96
- let _reserved = take(96)?;
-
- // off 304, size 2
- let isv_prod_id = u16::from_le_bytes(<[u8; 2]>::try_from(take(2)?)?);
-
- // off 306, size 2
- let isv_svn = u16::from_le_bytes(<[u8; 2]>::try_from(take(2)?)?);
-
- // off 308, size 60
- let _reserved = take(60)?;
-
- // off 368, size 64
- let mut report_data = [0u8; 64];
- let _report_data = take(64)?;
- let mut _it = _report_data.iter();
- for i in report_data.iter_mut() {
- *i = *_it.next().ok_or_else(|| anyhow!("Quote parsing error."))?;
- }
-
- if pos != bytes.len() {
- return Err(anyhow!("Quote parsing error."));
- }
-
- Ok(Self {
- version,
- signature_type,
- gid,
- isv_svn_qe,
- isv_svn_pce,
- qe_vendor_id,
- user_data,
- report_body: SgxReport {
- cpu_svn,
- misc_select,
- attributes,
- mr_enclave,
- mr_signer,
- isv_prod_id,
- isv_svn,
- report_data,
- },
- })
- }
-}
-
-pub struct SgxQuote {
- pub freshness: Duration,
- pub status: SgxQuoteStatus,
- pub body: SgxQuoteBody,
-}
-
-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))?;
-
- 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(QuoteParsingError::InvalidCertFormat))?;
- let sig_raw = iter
- .next()
- .ok_or_else(|| Error::new(QuoteParsingError::InvalidCertFormat))?;
- let sig = base64::decode(&sig_raw)?;
- let sig_cert_raw = iter
- .next()
- .ok_or_else(|| Error::new(QuoteParsingError::InvalidCertFormat))?;
- let sig_cert_dec = base64::decode_config(&sig_cert_raw, base64::STANDARD)?;
-
- let sig_cert = webpki::EndEntityCert::from(&sig_cert_dec)?;
-
- let mut root_store = rustls::RootCertStore::empty();
- root_store
- .add(&rustls::Certificate(ias_report_ca_cert.to_vec()))
- .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_report_ca_cert];
-
- let now_func = webpki::Time::try_from(SystemTime::now())
- .map_err(|_| anyhow!("Cannot convert time."))?;
-
- sig_cert.verify_is_valid_tls_server_cert(
- SUPPORTED_SIG_ALGS,
- &webpki::TLSServerTrustAnchors(&trust_anchors),
- &chain,
- now_func,
- )?;
-
- // Verify the signature against the signing cert
- sig_cert.verify_signature(&webpki::RSA_PKCS1_2048_8192_SHA256, &attn_report_raw, &sig)?;
-
- // Verify attestation report
- let attn_report: Value = serde_json::from_slice(attn_report_raw)?;
-
- // 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(QuoteParsingError::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())?
- };
-
- // 2. Get quote status
- let quote_status = {
- let status_string = attn_report["isvEnclaveQuoteStatus"]
- .as_str()
- .ok_or_else(|| Error::new(QuoteParsingError::BadAttnReport))?;
-
- SgxQuoteStatus::from(status_string)
- };
-
- // 3. Get quote body
- let quote_body = {
- let quote_encoded = attn_report["isvEnclaveQuoteBody"]
- .as_str()
- .ok_or_else(|| Error::new(QuoteParsingError::BadAttnReport))?;
- let quote_raw = base64::decode(&(quote_encoded.as_bytes()))?;
- SgxQuoteBody::parse_from(quote_raw.as_slice())?
- };
-
- 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(QuoteParsingError::BadAttnReport));
- }
-
- Ok(SgxQuote {
- freshness: std::time::Duration::from_secs(quote_freshness),
- status: quote_status,
- body: quote_body,
- })
- }
-}
diff --git a/attestation/src/report.rs b/attestation/src/report.rs
index af9bece..71331b5 100644
--- a/attestation/src/report.rs
+++ b/attestation/src/report.rs
@@ -15,188 +15,326 @@
// specific language governing permissions and limitations
// under the License.
-use crate::ias::IasClient;
-use crate::AttestationError;
-use anyhow::Error;
-use anyhow::Result;
-use hex;
-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::*;
+#![allow(clippy::redundant_closure)]
+
+// Insert std prelude in the top for the sgx feature
+#[cfg(feature = "mesalock_sgx")]
use std::prelude::v1::*;
-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;
+use crate::ias::IasReport;
+use anyhow::{anyhow, bail};
+use anyhow::{Error, Result};
+use chrono::DateTime;
+use rustls;
+use serde_json;
+use serde_json::Value;
+use std::convert::TryFrom;
+use std::time::*;
+
+#[cfg(feature = "mesalock_sgx")]
+use std::untrusted::time::SystemTimeEx;
+
+use uuid::Uuid;
+
+type SignatureAlgorithms = &'static [&'static webpki::SignatureAlgorithm];
+static SUPPORTED_SIG_ALGS: SignatureAlgorithms = &[
+ &webpki::ECDSA_P256_SHA256,
+ &webpki::ECDSA_P256_SHA384,
+ &webpki::ECDSA_P384_SHA256,
+ &webpki::ECDSA_P384_SHA384,
+ &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
+ &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
+ &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
+ &webpki::RSA_PKCS1_2048_8192_SHA256,
+ &webpki::RSA_PKCS1_2048_8192_SHA384,
+ &webpki::RSA_PKCS1_2048_8192_SHA512,
+ &webpki::RSA_PKCS1_3072_8192_SHA384,
+];
+
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum QuoteParsingError {
+ #[error("Invalid cert format")]
+ InvalidCertFormat,
+ #[error("Bad attestation report")]
+ BadAttnReport,
}
-#[derive(Default)]
-pub struct IasReport {
- pub report: String,
- pub signature: String,
- pub signing_cert: String,
+pub struct SgxReport {
+ pub cpu_svn: [u8; 16],
+ pub misc_select: u32,
+ pub attributes: [u8; 16],
+ pub mr_enclave: [u8; 32],
+ pub mr_signer: [u8; 32],
+ pub isv_prod_id: u16,
+ pub isv_svn: u16,
+ pub report_data: [u8; 64],
}
-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)
- }
+pub enum SgxQuoteVersion {
+ V1,
+ V2,
+}
- 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;
+pub enum SgxQuoteSigType {
+ Unlinkable,
+ Linkable,
+}
- let res = unsafe { ocall_sgx_init_quote(&mut rt as _, &mut ti as _, &mut eg as _) };
+#[derive(PartialEq, Debug)]
+pub enum SgxQuoteStatus {
+ OK,
+ GroupOutOfDate,
+ ConfigurationNeeded,
+ UnknownBadStatus,
+}
- if res != sgx_status_t::SGX_SUCCESS || rt != sgx_status_t::SGX_SUCCESS {
- Err(Error::new(AttestationError::OCallError))
- } else {
- Ok((ti, eg))
+impl From<&str> for SgxQuoteStatus {
+ fn from(status: &str) -> Self {
+ match status {
+ "OK" => SgxQuoteStatus::OK,
+ "GROUP_OUT_OF_DATE" => SgxQuoteStatus::GroupOutOfDate,
+ "CONFIGURATION_NEEDED" => SgxQuoteStatus::ConfigurationNeeded,
+ _ => SgxQuoteStatus::UnknownBadStatus,
}
}
+}
- 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))
- }
+pub struct SgxQuoteBody {
+ pub version: SgxQuoteVersion,
+ pub signature_type: SgxQuoteSigType,
+ pub gid: u32,
+ pub isv_svn_qe: u16,
+ pub isv_svn_pce: u16,
+ pub qe_vendor_id: Uuid,
+ pub user_data: [u8; 20],
+ pub report_body: SgxReport,
+}
+
+impl SgxQuoteBody {
+ fn parse_from<'a>(bytes: &'a [u8]) -> Result<Self> {
+ let mut pos: usize = 0;
+ // TODO: It is really unnecessary to construct a Vec<u8> each time.
+ // Try to optimize this.
+ let mut take = |n: usize| -> Result<&'a [u8]> {
+ if n > 0 && bytes.len() >= pos + n {
+ let ret = &bytes[pos..pos + n];
+ pos += n;
+ Ok(ret)
+ } else {
+ bail!("Quote parsing error.")
+ }
+ };
- fn get_quote(
- sigrl: &[u8],
- report: sgx_report_t,
- target_info: sgx_target_info_t,
- ias_spid_str: &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)
+ // off 0, size 2
+ let version = match u16::from_le_bytes(<[u8; 2]>::try_from(take(2)?)?) {
+ 1 => SgxQuoteVersion::V1,
+ 2 => SgxQuoteVersion::V2,
+ _ => bail!("Quote parsing error."),
};
- 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 _)
+ // off 2, size 2
+ let signature_type = match u16::from_le_bytes(<[u8; 2]>::try_from(take(2)?)?) {
+ 0 => SgxQuoteSigType::Unlinkable,
+ 1 => SgxQuoteSigType::Linkable,
+ _ => bail!("Quote parsing error."),
};
- if res != sgx_status_t::SGX_SUCCESS || rt != sgx_status_t::SGX_SUCCESS {
- return Err(Error::new(AttestationError::OCallError));
+ // off 4, size 4
+ let gid = u32::from_le_bytes(<[u8; 4]>::try_from(take(4)?)?);
+
+ // off 8, size 2
+ let isv_svn_qe = u16::from_le_bytes(<[u8; 2]>::try_from(take(2)?)?);
+
+ // off 10, size 2
+ let isv_svn_pce = u16::from_le_bytes(<[u8; 2]>::try_from(take(2)?)?);
+
+ // off 12, size 16
+ let qe_vendor_id_raw = <[u8; 16]>::try_from(take(16)?)?;
+ let qe_vendor_id = Uuid::from_slice(&qe_vendor_id_raw)?;
+
+ // off 28, size 20
+ let user_data = <[u8; 20]>::try_from(take(20)?)?;
+
+ // off 48, size 16
+ let cpu_svn = <[u8; 16]>::try_from(take(16)?)?;
+
+ // off 64, size 4
+ let misc_select = u32::from_le_bytes(<[u8; 4]>::try_from(take(4)?)?);
+
+ // off 68, size 28
+ let _reserved = take(28)?;
+
+ // off 96, size 16
+ let attributes = <[u8; 16]>::try_from(take(16)?)?;
+
+ // off 112, size 32
+ let mr_enclave = <[u8; 32]>::try_from(take(32)?)?;
+
+ // off 144, size 32
+ let _reserved = take(32)?;
+
+ // off 176, size 32
+ let mr_signer = <[u8; 32]>::try_from(take(32)?)?;
+
+ // off 208, size 96
+ let _reserved = take(96)?;
+
+ // off 304, size 2
+ let isv_prod_id = u16::from_le_bytes(<[u8; 2]>::try_from(take(2)?)?);
+
+ // off 306, size 2
+ let isv_svn = u16::from_le_bytes(<[u8; 2]>::try_from(take(2)?)?);
+
+ // off 308, size 60
+ let _reserved = take(60)?;
+
+ // off 368, size 64
+ let mut report_data = [0u8; 64];
+ let _report_data = take(64)?;
+ let mut _it = _report_data.iter();
+ for i in report_data.iter_mut() {
+ *i = *_it.next().ok_or_else(|| anyhow!("Quote parsing error."))?;
+ }
+
+ if pos != bytes.len() {
+ bail!("Quote parsing error.");
}
- 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 mut spid = sgx_types::sgx_spid_t::default();
- let hex = hex::decode(ias_spid_str)?;
- spid.id.copy_from_slice(&hex[..16]);
-
- 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,
- )
+ Ok(Self {
+ version,
+ signature_type,
+ gid,
+ isv_svn_qe,
+ isv_svn_pce,
+ qe_vendor_id,
+ user_data,
+ report_body: SgxReport {
+ cpu_svn,
+ misc_select,
+ attributes,
+ mr_enclave,
+ mr_signer,
+ isv_prod_id,
+ isv_svn,
+ report_data,
+ },
+ })
+ }
+}
+
+pub struct AttestationReport {
+ pub freshness: Duration,
+ pub sgx_quote_status: SgxQuoteStatus,
+ pub sgx_quote_body: SgxQuoteBody,
+}
+
+impl AttestationReport {
+ pub fn from_cert(cert: &[u8], ias_report_ca_cert: &[u8]) -> Result<Self> {
+ // Before we reach here, Webpki already verifed the cert is properly signed
+ use super::cert::*;
+
+ let x509 = yasna::parse_der(cert, |reader| X509::load(reader))?;
+
+ 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 report: IasReport = serde_json::from_slice(&payload)?;
+ let signing_cert = webpki::EndEntityCert::from(&report.signing_cert)?;
+
+ let mut root_store = rustls::RootCertStore::empty();
+ root_store
+ .add(&rustls::Certificate(ias_report_ca_cert.to_vec()))
+ .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_report_ca_cert];
+
+ let time = webpki::Time::try_from(SystemTime::now())
+ .map_err(|_| anyhow!("Cannot convert time."))?;
+
+ signing_cert.verify_is_valid_tls_server_cert(
+ SUPPORTED_SIG_ALGS,
+ &webpki::TLSServerTrustAnchors(&trust_anchors),
+ &chain,
+ time,
+ )?;
+
+ // Verify the signature against the signing cert
+ signing_cert.verify_signature(
+ &webpki::RSA_PKCS1_2048_8192_SHA256,
+ &report.report,
+ &report.signature,
+ )?;
+
+ // Verify attestation report
+ let attn_report: Value = serde_json::from_slice(&report.report)?;
+
+ // 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(QuoteParsingError::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())?
};
- if res != sgx_status_t::SGX_SUCCESS || rt != sgx_status_t::SGX_SUCCESS {
- return Err(Error::new(AttestationError::OCallError));
- }
+ // 2. Get quote status
+ let sgx_quote_status = {
+ let status_string = attn_report["isvEnclaveQuoteStatus"]
+ .as_str()
+ .ok_or_else(|| Error::new(QuoteParsingError::BadAttnReport))?;
- 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))?;
+ SgxQuoteStatus::from(status_string)
+ };
- // 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));
- }
+ // 3. Get quote body
+ let sgx_quote_body = {
+ let quote_encoded = attn_report["isvEnclaveQuoteBody"]
+ .as_str()
+ .ok_or_else(|| Error::new(QuoteParsingError::BadAttnReport))?;
+ let quote_raw = base64::decode(&(quote_encoded.as_bytes()))?;
+ SgxQuoteBody::parse_from(quote_raw.as_slice())?
+ };
+
+ let raw_pub_k = pub_k.to_bytes();
- // 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));
+ // 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 != &sgx_quote_body.report_body.report_data[..] {
+ bail!(QuoteParsingError::BadAttnReport);
}
- Ok(quote)
+ Ok(Self {
+ freshness: std::time::Duration::from_secs(quote_freshness),
+ sgx_quote_status,
+ sgx_quote_body,
+ })
}
}
diff --git a/attestation/src/verifier.rs b/attestation/src/verifier.rs
index 4d0561e..aee8a8e 100644
--- a/attestation/src/verifier.rs
+++ b/attestation/src/verifier.rs
@@ -15,7 +15,8 @@
// specific language governing permissions and limitations
// under the License.
-use crate::quote::SgxQuote;
+use crate::report::AttestationReport;
+use log::{debug, error};
use std::hash::{Hash, Hasher};
use std::vec::Vec;
use teaclave_config::build_config::BUILD_CONFIG;
@@ -42,31 +43,31 @@ impl Hash for EnclaveAttr {
}
#[derive(Clone)]
-pub struct SgxQuoteVerifier {
+pub struct AttestationReportVerifier {
pub enclave_attr: EnclaveAttr,
- pub verifier: fn(&SgxQuote) -> bool,
+ pub verifier: fn(&AttestationReport) -> bool,
}
-impl PartialEq for SgxQuoteVerifier {
- fn eq(&self, other: &SgxQuoteVerifier) -> bool {
+impl PartialEq for AttestationReportVerifier {
+ fn eq(&self, other: &AttestationReportVerifier) -> bool {
self.verifier as usize == other.verifier as usize && self.enclave_attr == other.enclave_attr
}
}
-impl Eq for SgxQuoteVerifier {}
+impl Eq for AttestationReportVerifier {}
-impl Hash for SgxQuoteVerifier {
+impl Hash for AttestationReportVerifier {
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
+fn universal_quote_verifier(report: &AttestationReport) -> bool {
+ report.sgx_quote_status != crate::report::SgxQuoteStatus::UnknownBadStatus
}
-impl SgxQuoteVerifier {
+impl AttestationReportVerifier {
pub fn new(enclave_attr: EnclaveAttr) -> Self {
Self {
enclave_attr,
@@ -74,9 +75,13 @@ impl SgxQuoteVerifier {
}
}
- 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;
+ fn verify_measures(&self, attestation_report: &AttestationReport) -> bool {
+ debug!("verify measures");
+ let this_mr_signer = attestation_report.sgx_quote_body.report_body.mr_signer;
+ let this_mr_enclave = attestation_report.sgx_quote_body.report_body.mr_enclave;
+ for m in self.enclave_attr.measures.iter() {
+ debug!("{:?}", m.mr_signer);
+ }
self.enclave_attr
.measures
@@ -85,14 +90,15 @@ impl SgxQuoteVerifier {
}
fn verify_cert(&self, cert_der: &[u8]) -> bool {
+ debug!("verify cert");
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(_) => {
+ let report = match AttestationReport::from_cert(&cert_der, BUILD_CONFIG.ias_root_ca_cert) {
+ Ok(report) => report,
+ Err(e) => {
+ error!("{:?}", e);
return false;
}
};
@@ -100,14 +106,14 @@ impl SgxQuoteVerifier {
// 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);
+ return (self.verifier)(&report);
}
- self.verify_measures("e) && (self.verifier)("e)
+ self.verify_measures(&report) && (self.verifier)(&report)
}
}
-impl rustls::ServerCertVerifier for SgxQuoteVerifier {
+impl rustls::ServerCertVerifier for AttestationReportVerifier {
fn verify_server_cert(
&self,
_roots: &rustls::RootCertStore,
@@ -129,7 +135,7 @@ impl rustls::ServerCertVerifier for SgxQuoteVerifier {
}
}
-impl rustls::ClientCertVerifier for SgxQuoteVerifier {
+impl rustls::ClientCertVerifier for AttestationReportVerifier {
fn client_auth_root_subjects(&self) -> rustls::DistinguishedNames {
rustls::DistinguishedNames::new()
}
diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml
index f95fd2a..49800fd 100644
--- a/rpc/Cargo.toml
+++ b/rpc/Cargo.toml
@@ -26,6 +26,7 @@ threadpool = { version = "1.8.0" }
webpki = { version = "0.21.0" }
teaclave_types = { path = "../types" }
+teaclave_attestation = { path = "../attestation" }
sgx_trts = { version = "1.1.0" }
sgx_tstd = { version = "1.1.0", features = ["net", "backtrace"], optional = true }
diff --git a/rpc/src/config.rs b/rpc/src/config.rs
index 5ecfd7d..3395616 100644
--- a/rpc/src/config.rs
+++ b/rpc/src/config.rs
@@ -1,6 +1,8 @@
use anyhow::Result;
use rustls;
use std::sync::Arc;
+use teaclave_attestation::verifier::AttestationReportVerifier;
+use teaclave_attestation::verifier::EnclaveAttr;
pub struct SgxTrustedTlsServerConfig {
pub config: Arc<rustls::ServerConfig>,
@@ -61,4 +63,17 @@ impl SgxTrustedTlsClientConfig {
config: Arc::new(config),
}
}
+
+ pub fn new_with_attestation_report_verifier(enclave_attr: EnclaveAttr) -> Self {
+ let mut config = rustls::ClientConfig::new();
+ let verifier = Arc::new(AttestationReportVerifier::new(enclave_attr));
+
+ config.dangerous().set_certificate_verifier(verifier);
+ config.versions.clear();
+ config.versions.push(rustls::ProtocolVersion::TLSv1_2);
+
+ Self {
+ config: Arc::new(config),
+ }
+ }
}
diff --git a/testsuite/service_functional_test/enclave/src/lib.rs b/testsuite/service_functional_test/enclave/src/lib.rs
index da1808a..e4b0f29 100644
--- a/testsuite/service_functional_test/enclave/src/lib.rs
+++ b/testsuite/service_functional_test/enclave/src/lib.rs
@@ -17,6 +17,7 @@
#![cfg_attr(feature = "mesalock_sgx", no_std)]
#[cfg(feature = "mesalock_sgx")]
+#[macro_use]
extern crate sgx_tstd as std;
#[macro_use]
diff --git a/testsuite/service_functional_test/enclave/src/teaclave_authentication_service.rs b/testsuite/service_functional_test/enclave/src/teaclave_authentication_service.rs
index 328bbf8..b3f46f2 100644
--- a/testsuite/service_functional_test/enclave/src/teaclave_authentication_service.rs
+++ b/testsuite/service_functional_test/enclave/src/teaclave_authentication_service.rs
@@ -1,8 +1,12 @@
use sgx_tunittest::*;
use std::prelude::v1::*;
+use teaclave_attestation::verifier::EnclaveAttr;
+use teaclave_config::runtime_config::RuntimeConfig;
use teaclave_proto::teaclave_authentication_service::*;
use teaclave_proto::teaclave_common::*;
+use teaclave_rpc::config::SgxTrustedTlsClientConfig;
use teaclave_rpc::endpoint::Endpoint;
+use teaclave_types::EnclaveInfo;
pub fn run_functional_tests() {
rsgx_unit_tests!(
@@ -14,7 +18,21 @@ pub fn run_functional_tests() {
}
fn test_login_success() {
- let channel = Endpoint::new("localhost:7776").connect().unwrap();
+ let runtime_config = RuntimeConfig::from_toml("runtime.config.toml").expect("runtime");
+ let enclave_info = EnclaveInfo::load_enclave_info(&runtime_config.audit.enclave_info);
+ let measure = enclave_info
+ .measurements
+ .get("teaclave_authentication_service")
+ .expect("authentication");
+ let enclave_attr = EnclaveAttr {
+ measures: vec![*measure],
+ };
+ let config = SgxTrustedTlsClientConfig::new_with_attestation_report_verifier(enclave_attr);
+
+ let channel = Endpoint::new("localhost:7776")
+ .config(config)
+ .connect()
+ .unwrap();
let mut client = TeaclaveAuthenticationClient::new(channel).unwrap();
let request = UserLoginRequest {
id: "test_id".to_string(),
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@teaclave.apache.org
For additional commands, e-mail: commits-help@teaclave.apache.org