blob: cbfdcf203cddb1fd25a0d3f9a9a677bf653fbb46 [file] [log] [blame]
// 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
}
}