mirror of
https://github.com/matter-labs/teepot.git
synced 2025-07-21 15:13:56 +02:00
ci: use crane
flake to build with nix
This enables to add cargo `fmt`, `clippy` and `deny` to nix, using cached results. Move the `teepot` crate to the `crates` subdir to make the life easier for the `crane` flake. Signed-off-by: Harald Hoyer <harald@matterlabs.dev>
This commit is contained in:
parent
1249048c93
commit
0654bacdb5
41 changed files with 323 additions and 150 deletions
50
crates/teepot/Cargo.toml
Normal file
50
crates/teepot/Cargo.toml
Normal file
|
@ -0,0 +1,50 @@
|
|||
[package]
|
||||
name = "teepot"
|
||||
description = "TEE secret manager"
|
||||
# no MIT license, because of copied code from:
|
||||
# * https://github.com/enarx/enarx
|
||||
# * https://github.com/enarx/sgx
|
||||
license = "Apache-2.0"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
actix-http.workspace = true
|
||||
actix-web.workspace = true
|
||||
anyhow.workspace = true
|
||||
awc.workspace = true
|
||||
bytemuck.workspace = true
|
||||
bytes.workspace = true
|
||||
clap.workspace = true
|
||||
const-oid.workspace = true
|
||||
enumset.workspace = true
|
||||
futures-core.workspace = true
|
||||
getrandom.workspace = true
|
||||
hex.workspace = true
|
||||
intel-tee-quote-verification-rs.workspace = true
|
||||
num-integer.workspace = true
|
||||
num-traits.workspace = true
|
||||
p256.workspace = true
|
||||
pgp.workspace = true
|
||||
pkcs8.workspace = true
|
||||
rand.workspace = true
|
||||
rsa.workspace = true
|
||||
rustls.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_with.workspace = true
|
||||
sha2.workspace = true
|
||||
signature.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
webpki-roots.workspace = true
|
||||
x509-cert.workspace = true
|
||||
zeroize.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow.workspace = true
|
||||
base64.workspace = true
|
||||
hex.workspace = true
|
||||
testaso.workspace = true
|
297
crates/teepot/src/client/mod.rs
Normal file
297
crates/teepot/src/client/mod.rs
Normal file
|
@ -0,0 +1,297 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
//! Helper functions for CLI clients to verify Intel SGX enclaves and other TEEs.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(clippy::all)]
|
||||
|
||||
pub mod vault;
|
||||
|
||||
use crate::server::pki::{RaTlsCollateralExtension, RaTlsQuoteExtension};
|
||||
use crate::sgx::Quote;
|
||||
pub use crate::sgx::{
|
||||
parse_tcb_levels, sgx_ql_qv_result_t, verify_quote_with_collateral, EnumSet,
|
||||
QuoteVerificationResult, TcbLevel,
|
||||
};
|
||||
use actix_web::http::header;
|
||||
use anyhow::Result;
|
||||
use awc::{Client, Connector};
|
||||
use clap::Args;
|
||||
use const_oid::AssociatedOid;
|
||||
use intel_tee_quote_verification_rs::Collateral;
|
||||
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier};
|
||||
use rustls::client::WebPkiServerVerifier;
|
||||
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
||||
use rustls::{ClientConfig, DigitallySignedStruct, Error, SignatureScheme};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use std::time::Duration;
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
use x509_cert::der::{Decode as _, Encode as _};
|
||||
use x509_cert::Certificate;
|
||||
|
||||
/// Options and arguments needed to attest a TEE
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct AttestationArgs {
|
||||
/// hex encoded SGX mrsigner of the enclave to attest
|
||||
#[arg(long)]
|
||||
pub sgx_mrsigner: Option<String>,
|
||||
/// hex encoded SGX mrenclave of the enclave to attest
|
||||
#[arg(long)]
|
||||
pub sgx_mrenclave: Option<String>,
|
||||
/// URL of the server
|
||||
#[arg(long, required = true)]
|
||||
pub server: String,
|
||||
/// allowed TCB levels, comma separated:
|
||||
/// Ok, ConfigNeeded, ConfigAndSwHardeningNeeded, SwHardeningNeeded, OutOfDate, OutOfDateConfigNeeded
|
||||
#[arg(long, value_parser = parse_tcb_levels)]
|
||||
pub sgx_allowed_tcb_levels: Option<EnumSet<TcbLevel>>,
|
||||
}
|
||||
|
||||
/// A connection to a TEE, which implements the `teepot` attestation API
|
||||
pub struct TeeConnection {
|
||||
/// Options and arguments needed to attest a TEE
|
||||
server: String,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl TeeConnection {
|
||||
/// Create a new connection to a TEE
|
||||
///
|
||||
/// This will verify the attestation report and check that the enclave
|
||||
/// is running the expected code.
|
||||
pub fn new(args: &AttestationArgs) -> Self {
|
||||
let tls_config = Arc::new(
|
||||
ClientConfig::builder()
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(Self::make_verifier(args.clone())))
|
||||
.with_no_client_auth(),
|
||||
);
|
||||
|
||||
let agent = Client::builder()
|
||||
.add_default_header((header::USER_AGENT, "teepot/1.0"))
|
||||
// a "connector" wraps the stream into an encrypted connection
|
||||
.connector(Connector::new().rustls_0_22(tls_config))
|
||||
.timeout(Duration::from_secs(12000))
|
||||
.finish();
|
||||
|
||||
Self {
|
||||
server: args.server.clone(),
|
||||
client: agent,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new connection to a TEE
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe, because it does not verify the attestation report.
|
||||
pub unsafe fn new_from_client_without_attestation(server: String, client: Client) -> Self {
|
||||
Self { server, client }
|
||||
}
|
||||
|
||||
/// Get a reference to the agent, which can be used to make requests to the TEE
|
||||
///
|
||||
/// Note, that it will refuse to connect to any other TLS server than the one
|
||||
/// specified in the `AttestationArgs` of the `Self::new` function.
|
||||
pub fn client(&self) -> &Client {
|
||||
&self.client
|
||||
}
|
||||
|
||||
/// Get a reference to the server URL
|
||||
pub fn server(&self) -> &str {
|
||||
&self.server
|
||||
}
|
||||
|
||||
/// Save the hash of the public server key to `REPORT_DATA` to check
|
||||
/// the attestations against it and it does not change on reconnect.
|
||||
pub fn make_verifier(args: AttestationArgs) -> impl ServerCertVerifier {
|
||||
#[derive(Debug)]
|
||||
struct V {
|
||||
args: AttestationArgs,
|
||||
server_verifier: Arc<WebPkiServerVerifier>,
|
||||
}
|
||||
impl ServerCertVerifier for V {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &CertificateDer,
|
||||
_intermediates: &[CertificateDer],
|
||||
_server_name: &ServerName,
|
||||
_ocsp_response: &[u8],
|
||||
_now: UnixTime,
|
||||
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
|
||||
let cert = Certificate::from_der(end_entity.as_ref())
|
||||
.map_err(|e| Error::General(format!("Failed get certificate {e:?}")))?;
|
||||
let pub_key = cert
|
||||
.tbs_certificate
|
||||
.subject_public_key_info
|
||||
.to_der()
|
||||
.unwrap();
|
||||
|
||||
let hash = Sha256::digest(pub_key);
|
||||
|
||||
let exts = cert
|
||||
.tbs_certificate
|
||||
.extensions
|
||||
.ok_or_else(|| Error::General("Failed get quote in certificate".into()))?;
|
||||
|
||||
trace!("Get quote bytes!");
|
||||
|
||||
let quote_bytes = exts
|
||||
.iter()
|
||||
.find(|ext| ext.extn_id == RaTlsQuoteExtension::OID)
|
||||
.ok_or_else(|| Error::General("Failed get quote in certificate".into()))?
|
||||
.extn_value
|
||||
.as_bytes();
|
||||
|
||||
trace!("Get collateral bytes!");
|
||||
|
||||
let collateral = exts
|
||||
.iter()
|
||||
.find(|ext| ext.extn_id == RaTlsCollateralExtension::OID)
|
||||
.and_then(|ext| {
|
||||
serde_json::from_slice::<Collateral>(ext.extn_value.as_bytes())
|
||||
.map_err(|e| {
|
||||
debug!("Failed to get collateral in certificate {e:?}");
|
||||
trace!(
|
||||
"Failed to get collateral in certificate {:?}",
|
||||
String::from_utf8_lossy(ext.extn_value.as_bytes())
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
|
||||
if collateral.is_none() {
|
||||
debug!("Failed to get collateral in certificate");
|
||||
}
|
||||
|
||||
let quote = Quote::try_from_bytes(quote_bytes).map_err(|e| {
|
||||
Error::General(format!("Failed get quote in certificate {e:?}"))
|
||||
})?;
|
||||
|
||||
if "e.report_body.reportdata[..32] != hash.as_slice() {
|
||||
error!("Report data mismatch");
|
||||
return Err(Error::General("Report data mismatch".to_string()));
|
||||
} else {
|
||||
info!(
|
||||
"Report data matches `{}`",
|
||||
hex::encode("e.report_body.reportdata[..32])
|
||||
);
|
||||
}
|
||||
|
||||
let current_time: i64 = time::SystemTime::now()
|
||||
.duration_since(time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as _;
|
||||
|
||||
let QuoteVerificationResult {
|
||||
collateral_expired,
|
||||
result,
|
||||
quote,
|
||||
advisories,
|
||||
earliest_expiration_date,
|
||||
..
|
||||
} = verify_quote_with_collateral(quote_bytes, collateral.as_ref(), current_time)
|
||||
.unwrap();
|
||||
|
||||
if collateral_expired || result != sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK {
|
||||
if collateral_expired {
|
||||
error!(
|
||||
"Collateral is out of date! Expired {}",
|
||||
earliest_expiration_date
|
||||
);
|
||||
return Err(Error::General(format!(
|
||||
"Collateral is out of date! Expired {}",
|
||||
earliest_expiration_date
|
||||
)));
|
||||
}
|
||||
|
||||
let tcblevel = TcbLevel::from(result);
|
||||
if self
|
||||
.args
|
||||
.sgx_allowed_tcb_levels
|
||||
.map_or(true, |levels| !levels.contains(tcblevel))
|
||||
{
|
||||
error!("Quote verification result: {}", tcblevel);
|
||||
return Err(Error::General(format!(
|
||||
"Quote verification result: {}",
|
||||
tcblevel
|
||||
)));
|
||||
}
|
||||
|
||||
info!("TcbLevel is allowed: {}", tcblevel);
|
||||
}
|
||||
|
||||
for advisory in advisories {
|
||||
warn!("Info: Advisory ID: {advisory}");
|
||||
}
|
||||
|
||||
if let Some(mrsigner) = &self.args.sgx_mrsigner {
|
||||
let mrsigner_bytes = hex::decode(mrsigner)
|
||||
.map_err(|e| Error::General(format!("Failed to decode mrsigner: {}", e)))?;
|
||||
if quote.report_body.mrsigner[..] != mrsigner_bytes {
|
||||
return Err(Error::General(format!(
|
||||
"mrsigner mismatch: got {}, expected {}",
|
||||
hex::encode(quote.report_body.mrsigner),
|
||||
&mrsigner
|
||||
)));
|
||||
} else {
|
||||
info!("mrsigner `{mrsigner}` matches");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mrenclave) = &self.args.sgx_mrenclave {
|
||||
let mrenclave_bytes = hex::decode(mrenclave).map_err(|e| {
|
||||
Error::General(format!("Failed to decode mrenclave: {}", e))
|
||||
})?;
|
||||
if quote.report_body.mrenclave[..] != mrenclave_bytes {
|
||||
return Err(Error::General(format!(
|
||||
"mrenclave mismatch: got {}, expected {}",
|
||||
hex::encode(quote.report_body.mrenclave),
|
||||
&mrenclave
|
||||
)));
|
||||
} else {
|
||||
info!("mrenclave `{mrenclave}` matches");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(rustls::client::danger::ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &CertificateDer<'_>,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> std::result::Result<HandshakeSignatureValid, Error> {
|
||||
self.server_verifier
|
||||
.verify_tls12_signature(message, cert, dss)
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &CertificateDer<'_>,
|
||||
dss: &DigitallySignedStruct,
|
||||
) -> std::result::Result<HandshakeSignatureValid, Error> {
|
||||
self.server_verifier
|
||||
.verify_tls13_signature(message, cert, dss)
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
self.server_verifier.supported_verify_schemes()
|
||||
}
|
||||
}
|
||||
let mut root_store = rustls::RootCertStore::empty();
|
||||
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
let server_verifier = WebPkiServerVerifier::builder(Arc::new(root_store))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
V {
|
||||
args,
|
||||
server_verifier,
|
||||
}
|
||||
}
|
||||
}
|
368
crates/teepot/src/client/vault.rs
Normal file
368
crates/teepot/src/client/vault.rs
Normal file
|
@ -0,0 +1,368 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
//! Helper functions for CLI clients to verify Intel SGX enclaves and other TEEs.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(clippy::all)]
|
||||
|
||||
use super::{AttestationArgs, TeeConnection};
|
||||
use crate::json::http::{AuthRequest, AuthResponse};
|
||||
use crate::server::pki::make_self_signed_cert;
|
||||
use crate::server::{AnyHowResponseError, HttpResponseError, Status};
|
||||
pub use crate::sgx::{
|
||||
parse_tcb_levels, sgx_gramine_get_quote, sgx_ql_qv_result_t, tee_qv_get_collateral,
|
||||
verify_quote_with_collateral, Collateral, EnumSet, QuoteVerificationResult, TcbLevel,
|
||||
};
|
||||
use actix_http::error::PayloadError;
|
||||
use actix_web::http::header;
|
||||
use actix_web::ResponseError;
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use awc::error::{SendRequestError, StatusCode};
|
||||
use awc::{Client, ClientResponse, Connector};
|
||||
use bytes::Bytes;
|
||||
use futures_core::Stream;
|
||||
use rustls::ClientConfig;
|
||||
use serde_json::{json, Value};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use tracing::{debug, error, info, trace};
|
||||
|
||||
const VAULT_TOKEN_HEADER: &str = "X-Vault-Token";
|
||||
|
||||
/// Error returned when sending a request to Vault
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum VaultSendError {
|
||||
/// Error sending the request
|
||||
SendRequest(String),
|
||||
/// Error returned by the Vault API
|
||||
#[error(transparent)]
|
||||
Vault(#[from] HttpResponseError),
|
||||
}
|
||||
|
||||
impl Display for VaultSendError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VaultSendError::SendRequest(e) => write!(f, "VaultSendError: {}", e),
|
||||
VaultSendError::Vault(e) => write!(f, "VaultSendError: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
fn assert_send<T: Send>() {}
|
||||
let _ = assert_send::<VaultSendError>;
|
||||
};
|
||||
|
||||
impl From<VaultSendError> for HttpResponseError {
|
||||
fn from(value: VaultSendError) -> Self {
|
||||
match value {
|
||||
VaultSendError::SendRequest(e) => HttpResponseError::Anyhow(AnyHowResponseError {
|
||||
status_code: StatusCode::BAD_GATEWAY,
|
||||
error: anyhow!(e),
|
||||
}),
|
||||
VaultSendError::Vault(e) => e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A connection to a Vault TEE, which implements the `teepot` attestation API
|
||||
/// called by a TEE itself. This authenticates the TEE to Vault and gets a token,
|
||||
/// which can be used to access the Vault API.
|
||||
pub struct VaultConnection {
|
||||
/// Options and arguments needed to attest Vault
|
||||
pub conn: TeeConnection,
|
||||
key_hash: [u8; 64],
|
||||
client_token: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl VaultConnection {
|
||||
/// Create a new connection to Vault
|
||||
///
|
||||
/// This will verify the attestation report and check that the enclave
|
||||
/// is running the expected code.
|
||||
pub async fn new(args: &AttestationArgs, name: String) -> Result<Self> {
|
||||
let (key_hash, rustls_certificate, rustls_pk) =
|
||||
make_self_signed_cert("CN=localhost", None)?;
|
||||
|
||||
let tls_config = Arc::new(
|
||||
ClientConfig::builder()
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(TeeConnection::make_verifier(
|
||||
args.clone(),
|
||||
)))
|
||||
.with_client_auth_cert(vec![rustls_certificate], rustls_pk)?,
|
||||
);
|
||||
|
||||
let client = Client::builder()
|
||||
.add_default_header((header::USER_AGENT, "teepot/1.0"))
|
||||
// a "connector" wraps the stream into an encrypted connection
|
||||
.connector(Connector::new().rustls_0_22(tls_config))
|
||||
.timeout(time::Duration::from_secs(12000))
|
||||
.finish();
|
||||
|
||||
let mut this = Self {
|
||||
name,
|
||||
key_hash,
|
||||
conn: unsafe {
|
||||
TeeConnection::new_from_client_without_attestation(args.server.clone(), client)
|
||||
},
|
||||
client_token: Default::default(),
|
||||
};
|
||||
|
||||
this.client_token = this.auth().await?.auth.client_token;
|
||||
|
||||
trace!("Got Token: {:#?}", &this.client_token);
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
/// create a new [`VaultConnection`] to Vault from an existing connection
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe, because it does not verify the attestation report.
|
||||
pub unsafe fn new_from_client_without_attestation(
|
||||
server: String,
|
||||
client: Client,
|
||||
name: String,
|
||||
client_token: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
client_token,
|
||||
conn: unsafe { TeeConnection::new_from_client_without_attestation(server, client) },
|
||||
key_hash: [0u8; 64],
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the agent, which can be used to make requests to the TEE
|
||||
///
|
||||
/// Note, that it will refuse to connect to any other TLS server than the one
|
||||
/// specified in the `AttestationArgs` of the `Self::new` function.
|
||||
pub fn agent(&self) -> &Client {
|
||||
self.conn.client()
|
||||
}
|
||||
|
||||
async fn auth(&self) -> Result<AuthResponse> {
|
||||
info!("Getting attestation report");
|
||||
let attestation_url = AuthRequest::URL;
|
||||
let quote = sgx_gramine_get_quote(&self.key_hash).context("Failed to get own quote")?;
|
||||
let collateral = tee_qv_get_collateral("e).context("Failed to get own collateral")?;
|
||||
|
||||
let auth_req = AuthRequest {
|
||||
name: self.name.clone(),
|
||||
tee_type: "sgx".to_string(),
|
||||
quote,
|
||||
collateral: serde_json::to_string(&collateral)?,
|
||||
challenge: None,
|
||||
};
|
||||
|
||||
let mut response = self
|
||||
.agent()
|
||||
.post(&format!(
|
||||
"{server}{attestation_url}",
|
||||
server = self.conn.server,
|
||||
))
|
||||
.send_json(&auth_req)
|
||||
.await
|
||||
.map_err(|e| anyhow!("Error sending attestation request: {}", e))?;
|
||||
|
||||
let status_code = response.status();
|
||||
if !status_code.is_success() {
|
||||
error!("Failed to login to vault: {}", status_code);
|
||||
if let Ok(r) = response.json::<Value>().await {
|
||||
eprintln!("Failed to login to vault: {}", r);
|
||||
}
|
||||
bail!("failed to login to vault: {}", status_code);
|
||||
}
|
||||
|
||||
let auth_response: Value = response.json().await.context("failed to login to vault")?;
|
||||
trace!(
|
||||
"Got AuthResponse: {:?}",
|
||||
serde_json::to_string(&auth_response)
|
||||
);
|
||||
|
||||
let auth_response: AuthResponse =
|
||||
serde_json::from_value(auth_response).context("Failed to parse AuthResponse")?;
|
||||
|
||||
trace!("Got AuthResponse: {:#?}", &auth_response);
|
||||
|
||||
Ok(auth_response)
|
||||
}
|
||||
|
||||
/// Send a put request to the vault
|
||||
pub async fn vault_put(
|
||||
&self,
|
||||
action: &str,
|
||||
url: &str,
|
||||
json: &Value,
|
||||
) -> std::result::Result<(StatusCode, Option<Value>), VaultSendError> {
|
||||
let full_url = format!("{}{url}", self.conn.server);
|
||||
info!("{action} via put {full_url}");
|
||||
debug!(
|
||||
"sending json: {:?}",
|
||||
serde_json::to_string(json).unwrap_or_default()
|
||||
);
|
||||
let res = self
|
||||
.agent()
|
||||
.put(full_url)
|
||||
.insert_header((VAULT_TOKEN_HEADER, self.client_token.clone()))
|
||||
.send_json(json)
|
||||
.await;
|
||||
Self::handle_client_response(action, res).await
|
||||
}
|
||||
|
||||
/// Send a get request to the vault
|
||||
pub async fn vault_get(
|
||||
&self,
|
||||
action: &str,
|
||||
url: &str,
|
||||
) -> std::result::Result<(StatusCode, Option<Value>), VaultSendError> {
|
||||
let full_url = format!("{}{url}", self.conn.server);
|
||||
info!("{action} via get {full_url}");
|
||||
let res = self
|
||||
.agent()
|
||||
.get(full_url)
|
||||
.insert_header((VAULT_TOKEN_HEADER, self.client_token.clone()))
|
||||
.send()
|
||||
.await;
|
||||
Self::handle_client_response(action, res).await
|
||||
}
|
||||
|
||||
async fn handle_client_response<S>(
|
||||
action: &str,
|
||||
res: std::result::Result<ClientResponse<S>, SendRequestError>,
|
||||
) -> std::result::Result<(StatusCode, Option<Value>), VaultSendError>
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, PayloadError>>,
|
||||
{
|
||||
match res {
|
||||
Ok(mut r) => {
|
||||
let status_code = r.status();
|
||||
if status_code.is_success() {
|
||||
let msg = r.json().await.ok();
|
||||
debug!(
|
||||
"{action}: status code: {status_code} {:?}",
|
||||
serde_json::to_string(&msg)
|
||||
);
|
||||
Ok((status_code, msg))
|
||||
} else {
|
||||
let err = HttpResponseError::from_proxy(r).await;
|
||||
error!("{action}: {err:?}");
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}: {}", action, e);
|
||||
Err(VaultSendError::SendRequest(e.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Revoke the token
|
||||
pub async fn revoke_token(&self) -> std::result::Result<(), VaultSendError> {
|
||||
self.vault_put(
|
||||
"Revoke the token",
|
||||
"/v1/auth/token/revoke-self",
|
||||
&Value::default(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_rel_path(rel_path: &str) -> Result<(), HttpResponseError> {
|
||||
if !rel_path.is_ascii() {
|
||||
return Err(anyhow!("path is not ascii")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
// check if rel_path is alphanumeric
|
||||
if !rel_path
|
||||
.chars()
|
||||
.all(|c| c.is_alphanumeric() || c == '_' || c == '/')
|
||||
{
|
||||
return Err(anyhow!("path is not alphanumeric")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// set a secret in the vault
|
||||
pub async fn store_secret<'de, T: serde::Serialize>(
|
||||
&self,
|
||||
val: T,
|
||||
rel_path: &str,
|
||||
) -> Result<(), HttpResponseError> {
|
||||
self.store_secret_for_tee(&self.name, val, rel_path).await
|
||||
}
|
||||
|
||||
/// set a secret in the vault for a different TEE
|
||||
pub async fn store_secret_for_tee<'de, T: serde::Serialize>(
|
||||
&self,
|
||||
tee_name: &str,
|
||||
val: T,
|
||||
rel_path: &str,
|
||||
) -> Result<(), HttpResponseError> {
|
||||
Self::check_rel_path(rel_path)?;
|
||||
|
||||
let value = serde_json::to_value(val)
|
||||
.context("converting value to json")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let value = json!({ "data" : { "_" : value } });
|
||||
|
||||
self.vault_put(
|
||||
"Setting secret",
|
||||
&format!("/v1/secret/data/tee/{}/{}", tee_name, rel_path),
|
||||
&value,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// get a secret from the vault
|
||||
pub async fn load_secret<'de, T: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
rel_path: &str,
|
||||
) -> Result<Option<T>, HttpResponseError> {
|
||||
self.load_secret_for_tee(&self.name, rel_path).await
|
||||
}
|
||||
|
||||
/// get a secret from the vault for a specific TEE
|
||||
pub async fn load_secret_for_tee<'de, T: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
tee_name: &str,
|
||||
rel_path: &str,
|
||||
) -> Result<Option<T>, HttpResponseError> {
|
||||
Self::check_rel_path(rel_path)?;
|
||||
let v = self
|
||||
.vault_get(
|
||||
"Getting secret",
|
||||
&format!("/v1/secret/data/tee/{}/{}", tee_name, rel_path),
|
||||
)
|
||||
.await
|
||||
.or_else(|e| match e {
|
||||
VaultSendError::Vault(ref se) => {
|
||||
if se.status_code() == StatusCode::NOT_FOUND {
|
||||
debug!("Secret not found: {}", rel_path);
|
||||
Ok((StatusCode::OK, None))
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
VaultSendError::SendRequest(_) => Err(e),
|
||||
})?
|
||||
.1
|
||||
.as_ref()
|
||||
.and_then(|v| v.get("data"))
|
||||
.and_then(|v| v.get("data"))
|
||||
.and_then(|v| v.get("_"))
|
||||
.and_then(|v| serde_json::from_value(v.clone()).transpose())
|
||||
.transpose()
|
||||
.context("Error getting value from vault")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
.flatten();
|
||||
Ok(v)
|
||||
}
|
||||
}
|
277
crates/teepot/src/json/http.rs
Normal file
277
crates/teepot/src/json/http.rs
Normal file
|
@ -0,0 +1,277 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
//! Common types for the teepot http JSON API
|
||||
|
||||
use crate::sgx::Collateral;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use serde_with::base64::Base64;
|
||||
use serde_with::serde_as;
|
||||
use std::fmt::Display;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The unseal request data
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Unseal {
|
||||
/// The unseal key
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
impl Unseal {
|
||||
/// The unseal URL
|
||||
pub const URL: &'static str = "/v1/sys/unseal";
|
||||
}
|
||||
|
||||
/// The attestation response
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AttestationResponse {
|
||||
/// The quote
|
||||
pub quote: Arc<[u8]>,
|
||||
/// The collateral
|
||||
pub collateral: Arc<Collateral>,
|
||||
}
|
||||
|
||||
/// The init request data
|
||||
#[serde_as]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Init {
|
||||
/// PGP keys to encrypt the unseal keys with
|
||||
pub pgp_keys: Vec<String>,
|
||||
/// number of secret shares
|
||||
pub secret_shares: usize,
|
||||
/// secret threshold
|
||||
pub secret_threshold: usize,
|
||||
/// PGP keys to sign commands for the admin tee
|
||||
#[serde_as(as = "Box<[Base64]>")]
|
||||
pub admin_pgp_keys: Box<[Box<[u8]>]>,
|
||||
/// admin threshold
|
||||
pub admin_threshold: usize,
|
||||
/// admin TEE mrenclave
|
||||
pub admin_tee_mrenclave: String,
|
||||
}
|
||||
|
||||
impl Init {
|
||||
/// The init URL
|
||||
pub const URL: &'static str = "/v1/sys/init";
|
||||
}
|
||||
|
||||
/// The init request data
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct VaultInitRequest {
|
||||
/// PGP keys to encrypt the unseal keys with
|
||||
pub pgp_keys: Vec<String>,
|
||||
/// number of secret shares
|
||||
pub secret_shares: usize,
|
||||
/// secret threshold
|
||||
pub secret_threshold: usize,
|
||||
}
|
||||
|
||||
/// The init response data
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct InitResponse {
|
||||
/// The unseal keys (gpg encrypted)
|
||||
pub unseal_keys: Vec<String>,
|
||||
}
|
||||
|
||||
/// The Vault TEE auth request data
|
||||
#[serde_as]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AuthRequest {
|
||||
/// The name of the TEE
|
||||
pub name: String,
|
||||
/// The type of the TEE
|
||||
#[serde(rename = "type")]
|
||||
pub tee_type: String,
|
||||
/// The attestation report data base64 encoded
|
||||
#[serde_as(as = "Base64")]
|
||||
pub quote: Box<[u8]>,
|
||||
/// The attestation collateral json encoded
|
||||
pub collateral: String,
|
||||
/// The vault attestation challenge (hex encoded)
|
||||
#[serde_as(as = "Option<serde_with::hex::Hex>")]
|
||||
#[serde(skip_serializing_if = "Option::is_none", default = "Option::default")]
|
||||
pub challenge: Option<[u8; 32]>,
|
||||
}
|
||||
|
||||
impl AuthRequest {
|
||||
/// The auth URL
|
||||
pub const URL: &'static str = "/v1/auth/tee/login";
|
||||
}
|
||||
|
||||
/// Vault auth metadata
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct AuthMetadataField {
|
||||
collateral_expiration_date: String,
|
||||
tee_name: String,
|
||||
}
|
||||
|
||||
/// Vault auth data
|
||||
#[serde_as]
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct AuthDataField {
|
||||
/// The attestation report data base64 encoded
|
||||
#[serde_as(as = "Base64")]
|
||||
#[serde(default)]
|
||||
pub quote: Box<[u8]>,
|
||||
/// The attestation collateral json encoded
|
||||
#[serde(default)]
|
||||
pub collateral: String,
|
||||
}
|
||||
|
||||
/// Vault auth
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct AuthField {
|
||||
/// TODO
|
||||
pub renewable: bool,
|
||||
/// TODO
|
||||
pub lease_duration: isize,
|
||||
/// TODO
|
||||
pub policies: Vec<String>,
|
||||
/// TODO
|
||||
pub accessor: String,
|
||||
/// TODO
|
||||
pub client_token: String,
|
||||
/// TODO
|
||||
pub metadata: AuthMetadataField,
|
||||
}
|
||||
|
||||
/// The Vault TEE auth response data
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct AuthResponse {
|
||||
/// vault auth
|
||||
pub auth: AuthField,
|
||||
///
|
||||
pub data: AuthDataField,
|
||||
}
|
||||
|
||||
/// One command datum
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct VaultCommand {
|
||||
/// The command to execute
|
||||
pub url: String,
|
||||
/// The command to execute
|
||||
pub data: Value,
|
||||
}
|
||||
|
||||
impl Display for VaultCommand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if f.alternate() {
|
||||
f.write_str(
|
||||
serde_json::to_string_pretty(self)
|
||||
.unwrap_or("{}".into())
|
||||
.as_str(),
|
||||
)
|
||||
} else {
|
||||
f.write_str(serde_json::to_string(self).unwrap_or("{}".into()).as_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Multiple command data
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct VaultCommands {
|
||||
/// The sha-256 hash of the last command hex encoded
|
||||
pub last_digest: String,
|
||||
/// The actual commands
|
||||
pub commands: Vec<VaultCommand>,
|
||||
}
|
||||
|
||||
/// The command request data
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct VaultCommandRequest {
|
||||
/// The commands to execute
|
||||
///
|
||||
/// The commands are json serialized `VaultCommands`,
|
||||
/// because they are signed with multiple signatures.
|
||||
///
|
||||
/// The commands are executed in order.
|
||||
pub commands: String,
|
||||
/// The signatures of the commands
|
||||
pub signatures: Vec<String>,
|
||||
}
|
||||
|
||||
impl VaultCommandRequest {
|
||||
/// The command request URL
|
||||
pub const URL: &'static str = "/v1/command";
|
||||
}
|
||||
|
||||
/// The command response
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct VaultCommandResponse {
|
||||
/// The status code
|
||||
pub status_code: u16,
|
||||
/// The response body
|
||||
pub value: Option<Value>,
|
||||
}
|
||||
|
||||
/// The command response
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct VaultCommandsResponse {
|
||||
/// The stored digest for the execution
|
||||
pub digest: String,
|
||||
/// The results of the individual commands
|
||||
pub results: Vec<VaultCommandResponse>,
|
||||
}
|
||||
|
||||
impl Display for VaultCommandResponse {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if f.alternate() {
|
||||
f.write_str(
|
||||
serde_json::to_string_pretty(self)
|
||||
.unwrap_or("{}".into())
|
||||
.as_str(),
|
||||
)
|
||||
} else {
|
||||
f.write_str(serde_json::to_string(self).unwrap_or("{}".into()).as_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The command request URL
|
||||
pub const DIGEST_URL: &str = "/v1/digest";
|
||||
|
||||
/// The signing request
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SignRequest {
|
||||
/// json serialized `SignRequestData`, because it is signed with multiple signatures.
|
||||
pub sign_request_data: String,
|
||||
/// The signatures of the SignRequestData
|
||||
pub signatures: Vec<String>,
|
||||
}
|
||||
|
||||
impl SignRequest {
|
||||
/// The sign request URL
|
||||
pub const URL: &'static str = "/v1/sign";
|
||||
}
|
||||
|
||||
/// The signing request data
|
||||
#[serde_as]
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct SignRequestData {
|
||||
/// The sha-256 hash of the last command hex encoded
|
||||
pub last_digest: String,
|
||||
/// The name of the TEE
|
||||
pub tee_name: String,
|
||||
/// The type of the TEE
|
||||
#[serde(rename = "type")]
|
||||
pub tee_type: String,
|
||||
/// The TEE security version number
|
||||
pub tee_svn: u16,
|
||||
/// The data to be signed.
|
||||
///
|
||||
/// In case of `tee_type == "sgx"`, it's the SGX Sigstruct Body
|
||||
#[serde_as(as = "Base64")]
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// The signing request
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SignResponse {
|
||||
/// The stored digest for the execution
|
||||
pub digest: String,
|
||||
/// The signed data for the tee.
|
||||
///
|
||||
/// In case of `tee_type == "sgx"`, it's the SGX Sigstruct
|
||||
pub signed_data: Vec<u8>,
|
||||
}
|
10
crates/teepot/src/json/mod.rs
Normal file
10
crates/teepot/src/json/mod.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
|
||||
//! Common types for the teepot JSON API
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(clippy::all)]
|
||||
|
||||
pub mod http;
|
||||
pub mod secrets;
|
34
crates/teepot/src/json/secrets.rs
Normal file
34
crates/teepot/src/json/secrets.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
|
||||
//! Common types for the teepot secrets JSON API
|
||||
|
||||
use crate::sgx::sign::Zeroizing;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::base64::Base64;
|
||||
use serde_with::serde_as;
|
||||
|
||||
/// Configuration for the admin tee
|
||||
#[serde_as]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct AdminConfig {
|
||||
/// PGP keys to sign commands for the admin tee
|
||||
#[serde_as(as = "Box<[Base64]>")]
|
||||
pub admin_pgp_keys: Box<[Box<[u8]>]>,
|
||||
/// admin threshold
|
||||
pub admin_threshold: usize,
|
||||
}
|
||||
|
||||
/// Configuration for the admin tee
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct AdminState {
|
||||
/// last digest of executed commands
|
||||
pub last_digest: String,
|
||||
}
|
||||
|
||||
/// Configuration for the admin tee
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct SGXSigningKey {
|
||||
/// private key in PEM format
|
||||
pub pem_pk: Zeroizing<String>,
|
||||
}
|
14
crates/teepot/src/lib.rs
Normal file
14
crates/teepot/src/lib.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
|
||||
//! Helper functions to verify Intel SGX enclaves and other TEEs.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(clippy::all)]
|
||||
|
||||
pub mod client;
|
||||
pub mod json;
|
||||
pub mod server;
|
||||
pub mod sgx;
|
||||
|
||||
pub mod quote;
|
40
crates/teepot/src/quote/mod.rs
Normal file
40
crates/teepot/src/quote/mod.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
//! Get a quote from a TEE
|
||||
|
||||
use crate::sgx::sgx_gramine_get_quote;
|
||||
use std::io;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
#[error("{msg}")]
|
||||
pub struct GetQuoteError {
|
||||
pub(crate) msg: Box<str>,
|
||||
#[source] // optional if field name is `source`
|
||||
pub(crate) source: io::Error,
|
||||
}
|
||||
|
||||
/// Get the attestation quote from a TEE
|
||||
pub fn get_quote(report_data: &[u8]) -> Result<Box<[u8]>, GetQuoteError> {
|
||||
// check, if we are running in a TEE
|
||||
if std::fs::metadata("/dev/attestation").is_ok() {
|
||||
if report_data.len() > 64 {
|
||||
return Err(GetQuoteError {
|
||||
msg: "Report data too long".into(),
|
||||
source: io::Error::new(io::ErrorKind::Other, "Report data too long"),
|
||||
});
|
||||
}
|
||||
|
||||
let mut report_data_fixed = [0u8; 64];
|
||||
report_data_fixed[..report_data.len()].copy_from_slice(report_data);
|
||||
|
||||
sgx_gramine_get_quote(&report_data_fixed)
|
||||
} else {
|
||||
// if not, return an error
|
||||
Err(GetQuoteError {
|
||||
msg: "Not running in a TEE".into(),
|
||||
source: io::Error::new(io::ErrorKind::Other, "Not running in a TEE"),
|
||||
})
|
||||
}
|
||||
}
|
147
crates/teepot/src/server/attestation.rs
Normal file
147
crates/teepot/src/server/attestation.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
//! Common attestation API for all TEEs
|
||||
|
||||
use crate::client::AttestationArgs;
|
||||
use crate::json::http::AttestationResponse;
|
||||
use crate::sgx::{
|
||||
parse_tcb_levels, sgx_gramine_get_quote, tee_qv_get_collateral, verify_quote_with_collateral,
|
||||
Collateral, EnumSet, QuoteVerificationResult, TcbLevel,
|
||||
};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use clap::Args;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
struct Attestation {
|
||||
quote: Arc<[u8]>,
|
||||
collateral: Arc<Collateral>,
|
||||
report_data: [u8; 64],
|
||||
earliest_expiration_date: i64,
|
||||
}
|
||||
|
||||
/// Returns the quote and collateral for the current TEE.
|
||||
///
|
||||
/// if `allowed_tcb_levels` is `None`, then any TCB level is accepted.
|
||||
/// Otherwise, the quote must be verified and the collateral must be
|
||||
/// within the allowed TCB levels.
|
||||
pub fn get_quote_and_collateral(
|
||||
allowed_tcb_levels: Option<EnumSet<TcbLevel>>,
|
||||
report_data: &[u8; 64],
|
||||
) -> Result<AttestationResponse> {
|
||||
static ATTESTATION: RwLock<Option<Attestation>> = RwLock::new(None);
|
||||
|
||||
let unix_time: i64 = std::time::SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as _;
|
||||
|
||||
if let Some(attestation) = ATTESTATION.read().unwrap().as_ref() {
|
||||
trace!(attestation.earliest_expiration_date);
|
||||
|
||||
if attestation.earliest_expiration_date > unix_time.saturating_add(60)
|
||||
&& report_data.eq(&attestation.report_data)
|
||||
{
|
||||
debug!("return cache attestation quote and collateral");
|
||||
return Ok(AttestationResponse {
|
||||
quote: attestation.quote.clone(),
|
||||
collateral: attestation.collateral.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let myquote = sgx_gramine_get_quote(report_data).context("Failed to get own quote")?;
|
||||
let collateral = tee_qv_get_collateral(&myquote).context("Failed to get own collateral")?;
|
||||
|
||||
let QuoteVerificationResult {
|
||||
collateral_expired,
|
||||
result,
|
||||
earliest_expiration_date,
|
||||
tcb_level_date_tag,
|
||||
quote,
|
||||
advisories,
|
||||
} = verify_quote_with_collateral(&myquote, Some(&collateral), unix_time.saturating_add(60))
|
||||
.context("Failed to verify own quote with collateral")?;
|
||||
|
||||
debug!(tcb_level_date_tag);
|
||||
|
||||
if collateral_expired {
|
||||
bail!("Freshly fetched collateral expired");
|
||||
}
|
||||
|
||||
let tcblevel = TcbLevel::from(result);
|
||||
if tcblevel != TcbLevel::Ok
|
||||
&& allowed_tcb_levels.map_or(false, |levels| !levels.contains(tcblevel))
|
||||
{
|
||||
error!("Quote verification result: {}", tcblevel);
|
||||
bail!("Quote verification result: {}", tcblevel);
|
||||
}
|
||||
|
||||
for advisory in advisories {
|
||||
warn!("\tInfo: Advisory ID: {advisory}");
|
||||
}
|
||||
|
||||
info!("Own quote verified successfully: {}", tcblevel);
|
||||
info!(
|
||||
"Earliest expiration in {:?}",
|
||||
Duration::from_secs((earliest_expiration_date - unix_time) as _)
|
||||
);
|
||||
info!("mrsigner: {}", hex::encode(quote.report_body.mrsigner));
|
||||
info!("mrenclave: {}", hex::encode(quote.report_body.mrenclave));
|
||||
|
||||
let quote: Arc<[u8]> = Arc::from(myquote);
|
||||
let collateral = Arc::from(collateral);
|
||||
|
||||
let mut attestation = ATTESTATION.write().unwrap();
|
||||
*attestation = Some(Attestation {
|
||||
quote: quote.clone(),
|
||||
collateral: collateral.clone(),
|
||||
report_data: *report_data,
|
||||
earliest_expiration_date,
|
||||
});
|
||||
|
||||
Ok(AttestationResponse { quote, collateral })
|
||||
}
|
||||
|
||||
/// Options and arguments needed to attest a TEE
|
||||
#[derive(Args, Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct VaultAttestationArgs {
|
||||
/// hex encoded SGX mrsigner of the enclave to attest
|
||||
#[arg(long, env = "VAULT_SGX_MRSIGNER")]
|
||||
pub vault_sgx_mrsigner: Option<String>,
|
||||
/// hex encoded SGX mrenclave of the enclave to attest
|
||||
#[arg(long, env = "VAULT_SGX_MRENCLAVE")]
|
||||
pub vault_sgx_mrenclave: Option<String>,
|
||||
/// URL of the server
|
||||
#[arg(long, required = true, env = "VAULT_ADDR")]
|
||||
pub vault_addr: String,
|
||||
/// allowed TCB levels, comma separated:
|
||||
/// Ok, ConfigNeeded, ConfigAndSwHardeningNeeded, SwHardeningNeeded, OutOfDate, OutOfDateConfigNeeded
|
||||
#[arg(long, value_parser = parse_tcb_levels, env = "VAULT_SGX_ALLOWED_TCB_LEVELS")]
|
||||
pub vault_sgx_allowed_tcb_levels: Option<EnumSet<TcbLevel>>,
|
||||
}
|
||||
|
||||
impl From<VaultAttestationArgs> for AttestationArgs {
|
||||
fn from(value: VaultAttestationArgs) -> Self {
|
||||
AttestationArgs {
|
||||
sgx_mrsigner: value.vault_sgx_mrsigner,
|
||||
sgx_mrenclave: value.vault_sgx_mrenclave,
|
||||
server: value.vault_addr,
|
||||
sgx_allowed_tcb_levels: value.vault_sgx_allowed_tcb_levels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&VaultAttestationArgs> for AttestationArgs {
|
||||
fn from(value: &VaultAttestationArgs) -> Self {
|
||||
AttestationArgs {
|
||||
sgx_mrsigner: value.vault_sgx_mrsigner.clone(),
|
||||
sgx_mrenclave: value.vault_sgx_mrenclave.clone(),
|
||||
server: value.vault_addr.clone(),
|
||||
sgx_allowed_tcb_levels: value.vault_sgx_allowed_tcb_levels,
|
||||
}
|
||||
}
|
||||
}
|
187
crates/teepot/src/server/mod.rs
Normal file
187
crates/teepot/src/server/mod.rs
Normal file
|
@ -0,0 +1,187 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
//! # tee-server
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(clippy::all)]
|
||||
|
||||
pub mod attestation;
|
||||
pub mod pki;
|
||||
pub mod signatures;
|
||||
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::Bytes;
|
||||
use actix_web::{error, HttpRequest, HttpResponse};
|
||||
use actix_web::{HttpMessage, ResponseError};
|
||||
use anyhow::anyhow;
|
||||
use awc::error::{PayloadError, SendRequestError};
|
||||
use awc::ClientResponse;
|
||||
use futures_core::Stream;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use tracing::error;
|
||||
|
||||
/// Anyhow error with an HTTP status code
|
||||
pub struct AnyHowResponseError {
|
||||
/// error message
|
||||
pub error: anyhow::Error,
|
||||
/// HTTP status code
|
||||
pub status_code: StatusCode,
|
||||
}
|
||||
|
||||
/// Proxy response error
|
||||
pub struct ProxyResponseError {
|
||||
/// HTTP status code
|
||||
pub status_code: StatusCode,
|
||||
/// HTTP body
|
||||
pub body: Option<Bytes>,
|
||||
/// HTTP content type
|
||||
pub content_type: String,
|
||||
}
|
||||
|
||||
/// custom HTTP response error
|
||||
pub enum HttpResponseError {
|
||||
/// Anyhow error
|
||||
Anyhow(AnyHowResponseError),
|
||||
/// Proxy error
|
||||
Proxy(ProxyResponseError),
|
||||
}
|
||||
|
||||
impl std::error::Error for HttpResponseError {}
|
||||
|
||||
/// Attach an HTTP status code to an anyhow error turning it into an HttpResponseError
|
||||
pub trait Status {
|
||||
/// The Ok type
|
||||
type Ok;
|
||||
/// Attach an HTTP status code to an anyhow error turning it into an HttpResponseError
|
||||
fn status(self, status: StatusCode) -> Result<Self::Ok, HttpResponseError>;
|
||||
}
|
||||
|
||||
impl<T> Status for Result<T, anyhow::Error> {
|
||||
type Ok = T;
|
||||
fn status(self, status: StatusCode) -> Result<T, HttpResponseError> {
|
||||
match self {
|
||||
Ok(value) => Ok(value),
|
||||
Err(error) => Err(HttpResponseError::new(error, status)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpResponseError {
|
||||
fn new(error: anyhow::Error, status_code: StatusCode) -> Self {
|
||||
Self::Anyhow(AnyHowResponseError { error, status_code })
|
||||
}
|
||||
|
||||
/// Create a new HTTP response error from a proxy response
|
||||
pub async fn from_proxy<S>(mut response: ClientResponse<S>) -> Self
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, PayloadError>>,
|
||||
{
|
||||
let status_code = response.status();
|
||||
let body = response.body().await.ok();
|
||||
let content_type = response.content_type().to_string();
|
||||
|
||||
error!(
|
||||
"Vault returned server error: {status_code} {}",
|
||||
body.as_ref()
|
||||
.map_or("", |b| std::str::from_utf8(b).unwrap_or(""))
|
||||
);
|
||||
|
||||
Self::Proxy(ProxyResponseError {
|
||||
status_code,
|
||||
body,
|
||||
content_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for HttpResponseError {
|
||||
fn from(value: &str) -> Self {
|
||||
error!("{}", value);
|
||||
HttpResponseError::new(
|
||||
anyhow!(value.to_string()),
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SendRequestError> for HttpResponseError {
|
||||
fn from(error: SendRequestError) -> Self {
|
||||
error!("Error sending request: {:?}", error);
|
||||
HttpResponseError::new(
|
||||
anyhow!(error.to_string()),
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for HttpResponseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if let Self::Anyhow(e) = self {
|
||||
if f.alternate() {
|
||||
write!(f, "{:#?}", e.error)
|
||||
} else {
|
||||
write!(f, "{:?}", e.error)
|
||||
}
|
||||
} else {
|
||||
write!(f, "HttpResponseError")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for HttpResponseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if let Self::Anyhow(e) = self {
|
||||
if f.alternate() {
|
||||
write!(f, "{:#}", e.error)
|
||||
} else {
|
||||
write!(f, "{}", e.error)
|
||||
}
|
||||
} else {
|
||||
write!(f, "HttpResponseError")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for HttpResponseError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
HttpResponseError::Anyhow(e) => e.status_code,
|
||||
HttpResponseError::Proxy(e) => e.status_code,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match self {
|
||||
HttpResponseError::Anyhow(e) => HttpResponse::build(self.status_code())
|
||||
.content_type("application/json")
|
||||
.body(format!(r#"{{"error":"{}"}}"#, e.error)),
|
||||
HttpResponseError::Proxy(e) => {
|
||||
if let Some(ref body) = e.body {
|
||||
HttpResponse::build(self.status_code())
|
||||
.content_type(e.content_type.clone())
|
||||
.body(body.clone())
|
||||
} else {
|
||||
HttpResponse::new(self.status_code())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new json config
|
||||
pub fn new_json_cfg() -> actix_web::web::JsonConfig {
|
||||
actix_web::web::JsonConfig::default()
|
||||
.limit(1024 * 1024)
|
||||
.error_handler(json_error_handler)
|
||||
}
|
||||
|
||||
fn json_error_handler(err: error::JsonPayloadError, _: &HttpRequest) -> actix_web::Error {
|
||||
error::InternalError::from_response(
|
||||
"",
|
||||
HttpResponse::BadRequest()
|
||||
.content_type("application/json")
|
||||
.body(format!(r#"{{"error":"json error: {}"}}"#, err)),
|
||||
)
|
||||
.into()
|
||||
}
|
283
crates/teepot/src/server/pki.rs
Normal file
283
crates/teepot/src/server/pki.rs
Normal file
|
@ -0,0 +1,283 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
//! Create a private key and a signed and self-signed certificates
|
||||
|
||||
use crate::quote::get_quote;
|
||||
use crate::sgx::tee_qv_get_collateral;
|
||||
pub use crate::sgx::{
|
||||
parse_tcb_levels, sgx_ql_qv_result_t, verify_quote_with_collateral, EnumSet,
|
||||
QuoteVerificationResult, TcbLevel,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use const_oid::db::rfc5280::{ID_KP_CLIENT_AUTH, ID_KP_SERVER_AUTH};
|
||||
use const_oid::AssociatedOid;
|
||||
use getrandom::getrandom;
|
||||
use p256::ecdsa::DerSignature;
|
||||
use p256::pkcs8::EncodePrivateKey;
|
||||
use pkcs8::der;
|
||||
use rustls::pki_types::PrivatePkcs8KeyDer;
|
||||
use sha2::{Digest, Sha256};
|
||||
use signature::Signer;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use tracing::debug;
|
||||
use x509_cert::builder::{Builder, CertificateBuilder, Profile};
|
||||
use x509_cert::der::pem::LineEnding;
|
||||
use x509_cert::der::{asn1::OctetString, Encode as _, EncodePem as _, Length};
|
||||
use x509_cert::ext::pkix::name::GeneralNames;
|
||||
use x509_cert::ext::pkix::{ExtendedKeyUsage, SubjectAltName};
|
||||
use x509_cert::ext::{AsExtension, Extension};
|
||||
use x509_cert::name::{Name, RdnSequence};
|
||||
use x509_cert::serial_number::SerialNumber;
|
||||
use x509_cert::spki::{
|
||||
DynSignatureAlgorithmIdentifier, EncodePublicKey, ObjectIdentifier, SignatureBitStringEncoding,
|
||||
SubjectPublicKeyInfoOwned,
|
||||
};
|
||||
use x509_cert::time::Validity;
|
||||
use x509_cert::Certificate;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
/// The OID for the `gramine-ra-tls` quote extension
|
||||
pub const ID_GRAMINE_RA_TLS_QUOTE: ObjectIdentifier =
|
||||
ObjectIdentifier::new_unwrap("1.2.840.113741.1337.6");
|
||||
|
||||
/// The OID for the `enarx-ra-tls` collateral extension
|
||||
/// TODO: this OID is just made up in `enarx` OID namespace, reserve it somehow
|
||||
pub const ID_GRAMINE_RA_TLS_COLLATERAL: ObjectIdentifier =
|
||||
ObjectIdentifier::new_unwrap("1.3.6.1.4.1.58270.1.99");
|
||||
|
||||
/// The `gramine-ra-tls` x509 extension
|
||||
pub struct RaTlsQuoteExtension {
|
||||
/// The hash of the certificate's public key
|
||||
pub quote: Vec<u8>,
|
||||
}
|
||||
|
||||
impl AssociatedOid for RaTlsQuoteExtension {
|
||||
const OID: ObjectIdentifier = ID_GRAMINE_RA_TLS_QUOTE;
|
||||
}
|
||||
|
||||
impl x509_cert::der::Encode for RaTlsQuoteExtension {
|
||||
fn encoded_len(&self) -> pkcs8::der::Result<Length> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn encode(
|
||||
&self,
|
||||
_writer: &mut impl x509_cert::der::Writer,
|
||||
) -> Result<(), x509_cert::der::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsExtension for RaTlsQuoteExtension {
|
||||
fn critical(&self, _: &x509_cert::name::Name, _: &[x509_cert::ext::Extension]) -> bool {
|
||||
false
|
||||
}
|
||||
fn to_extension(
|
||||
&self,
|
||||
_subject: &Name,
|
||||
_extensions: &[Extension],
|
||||
) -> std::result::Result<Extension, der::Error> {
|
||||
Ok(Extension {
|
||||
extn_id: <Self as AssociatedOid>::OID,
|
||||
critical: false,
|
||||
extn_value: OctetString::new(self.quote.as_slice())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The `gramine-ra-tls` x509 extension
|
||||
pub struct RaTlsCollateralExtension {
|
||||
/// The hash of the certificate's public key
|
||||
pub collateral: Vec<u8>,
|
||||
}
|
||||
|
||||
impl AssociatedOid for RaTlsCollateralExtension {
|
||||
const OID: ObjectIdentifier = ID_GRAMINE_RA_TLS_COLLATERAL;
|
||||
}
|
||||
|
||||
impl x509_cert::der::Encode for RaTlsCollateralExtension {
|
||||
fn encoded_len(&self) -> pkcs8::der::Result<Length> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn encode(
|
||||
&self,
|
||||
_writer: &mut impl x509_cert::der::Writer,
|
||||
) -> Result<(), x509_cert::der::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsExtension for RaTlsCollateralExtension {
|
||||
fn critical(&self, _: &x509_cert::name::Name, _: &[x509_cert::ext::Extension]) -> bool {
|
||||
false
|
||||
}
|
||||
fn to_extension(
|
||||
&self,
|
||||
_subject: &Name,
|
||||
_extensions: &[Extension],
|
||||
) -> std::result::Result<Extension, der::Error> {
|
||||
Ok(Extension {
|
||||
extn_id: <Self as AssociatedOid>::OID,
|
||||
critical: false,
|
||||
extn_value: OctetString::new(self.collateral.as_slice())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a private key and a self-signed certificate
|
||||
pub fn make_self_signed_cert(
|
||||
dn: &str,
|
||||
an: Option<GeneralNames>,
|
||||
) -> Result<(
|
||||
[u8; 64],
|
||||
rustls::pki_types::CertificateDer<'static>,
|
||||
rustls::pki_types::PrivateKeyDer<'static>,
|
||||
)> {
|
||||
// Generate a keypair.
|
||||
let mut rng = rand::thread_rng();
|
||||
let signing_key = p256::ecdsa::SigningKey::random(&mut rng);
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
let verifying_key_der = verifying_key
|
||||
.to_public_key_der()
|
||||
.context("failed to create public key der")?;
|
||||
|
||||
let mut key_hash = [0u8; 64];
|
||||
let hash = Sha256::digest(verifying_key_der.as_bytes());
|
||||
key_hash[..32].copy_from_slice(&hash);
|
||||
|
||||
let quote = get_quote(&key_hash)?;
|
||||
debug!("quote.len: {:?}", quote.len());
|
||||
// Create a relative distinguished name.
|
||||
let rdns = RdnSequence::from_str(dn)?;
|
||||
let collateral = tee_qv_get_collateral("e).context("Failed to get own collateral")?;
|
||||
|
||||
let mut serial = [0u8; 16];
|
||||
getrandom(&mut serial)?;
|
||||
|
||||
let mut builder = CertificateBuilder::new(
|
||||
Profile::Leaf {
|
||||
issuer: rdns.clone(),
|
||||
enable_key_agreement: true,
|
||||
enable_key_encipherment: true,
|
||||
},
|
||||
SerialNumber::new(&serial)?,
|
||||
Validity::from_now(Duration::from_secs(60 * 60 * 24 * 365 * 10))?,
|
||||
rdns,
|
||||
SubjectPublicKeyInfoOwned::try_from(verifying_key_der.as_bytes())
|
||||
.context("failed to create SubjectPublicKeyInfo")?,
|
||||
&signing_key,
|
||||
)
|
||||
.context("failed to create CertificateBuilder")?;
|
||||
|
||||
builder
|
||||
.add_extension(&ExtendedKeyUsage(vec![
|
||||
ID_KP_SERVER_AUTH,
|
||||
ID_KP_CLIENT_AUTH,
|
||||
]))
|
||||
.context("failed to add ExtendedKeyUsage")?;
|
||||
|
||||
if let Some(an) = an {
|
||||
builder
|
||||
.add_extension(&SubjectAltName(an))
|
||||
.context("failed to add SubjectAltName")?;
|
||||
}
|
||||
|
||||
builder
|
||||
.add_extension(&RaTlsQuoteExtension {
|
||||
quote: quote.to_vec(),
|
||||
})
|
||||
.context("failed to add GRAMINE_RA_TLS")?;
|
||||
|
||||
builder
|
||||
.add_extension(&RaTlsCollateralExtension {
|
||||
collateral: serde_json::to_vec(&collateral).context("failed to add GRAMINE_RA_TLS")?,
|
||||
})
|
||||
.context("failed to add GRAMINE_RA_TLS")?;
|
||||
|
||||
let crt = builder.build::<DerSignature>().unwrap();
|
||||
let rustls_certificate = rustls::pki_types::CertificateDer::from(crt.to_der()?);
|
||||
let signing_key_der = signing_key
|
||||
.to_pkcs8_der()
|
||||
.context("failed to encode PKCS#8")?;
|
||||
|
||||
let rustls_pk = rustls::pki_types::PrivateKeyDer::from(PrivatePkcs8KeyDer::from(
|
||||
signing_key_der.as_bytes(),
|
||||
))
|
||||
.clone_key();
|
||||
Ok((key_hash, rustls_certificate, rustls_pk))
|
||||
}
|
||||
|
||||
/// Create a private key and a self-signed certificate
|
||||
pub fn make_signed_cert<S, Signature>(
|
||||
dn: &str,
|
||||
an: Option<GeneralNames>,
|
||||
issuer_cert: &Certificate,
|
||||
issuer_key: &S,
|
||||
) -> Result<([u8; 64], String, Zeroizing<String>)>
|
||||
where
|
||||
Signature: SignatureBitStringEncoding,
|
||||
S: signature::Keypair + DynSignatureAlgorithmIdentifier + Signer<Signature>,
|
||||
S::VerifyingKey: EncodePublicKey,
|
||||
{
|
||||
// Generate a keypair.
|
||||
let mut rng = rand::thread_rng();
|
||||
let signing_key = p256::ecdsa::SigningKey::random(&mut rng);
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
let verifying_key_der = verifying_key
|
||||
.to_public_key_der()
|
||||
.context("failed to create public key der")?;
|
||||
|
||||
let mut key_hash = [0u8; 64];
|
||||
let hash = Sha256::digest(verifying_key_der.as_bytes());
|
||||
key_hash[..32].copy_from_slice(&hash);
|
||||
|
||||
let quote = get_quote(&key_hash).context("Failed to get own quote")?;
|
||||
|
||||
// Create a relative distinguished name.
|
||||
let subject = Name::from_str(dn)?;
|
||||
|
||||
let mut serial = [0u8; 16];
|
||||
getrandom(&mut serial)?;
|
||||
|
||||
let mut builder = CertificateBuilder::new(
|
||||
Profile::Leaf {
|
||||
issuer: issuer_cert.tbs_certificate.subject.clone(),
|
||||
enable_key_agreement: true,
|
||||
enable_key_encipherment: true,
|
||||
},
|
||||
SerialNumber::new(&serial)?,
|
||||
Validity::from_now(Duration::from_secs(60 * 60 * 24 * 365 * 10))?,
|
||||
subject,
|
||||
SubjectPublicKeyInfoOwned::try_from(verifying_key_der.as_bytes())
|
||||
.context("failed to create SubjectPublicKeyInfo")?,
|
||||
issuer_key,
|
||||
)
|
||||
.context("failed to create CertificateBuilder")?;
|
||||
builder
|
||||
.add_extension(&ExtendedKeyUsage(vec![
|
||||
ID_KP_SERVER_AUTH,
|
||||
ID_KP_CLIENT_AUTH,
|
||||
]))
|
||||
.context("failed to add ExtendedKeyUsage")?;
|
||||
|
||||
if let Some(an) = an {
|
||||
builder
|
||||
.add_extension(&SubjectAltName(an))
|
||||
.context("failed to add SubjectAltName")?;
|
||||
}
|
||||
|
||||
builder
|
||||
.add_extension(&RaTlsQuoteExtension {
|
||||
quote: quote.to_vec(),
|
||||
})
|
||||
.context("failed to add GRAMINE_RA_TLS")?;
|
||||
|
||||
let crt = builder.build::<Signature>().unwrap();
|
||||
let cert_pem = crt.to_pem(LineEnding::LF)?;
|
||||
let key_pem = signing_key.to_pkcs8_pem(LineEnding::LF)?;
|
||||
|
||||
Ok((key_hash, cert_pem, key_pem))
|
||||
}
|
120
crates/teepot/src/server/signatures.rs
Normal file
120
crates/teepot/src/server/signatures.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
|
||||
//! Signature checking utilities
|
||||
|
||||
use crate::json::secrets::AdminConfig;
|
||||
use crate::server::{HttpResponseError, Status as _};
|
||||
use actix_web::http::StatusCode;
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use pgp::types::KeyTrait;
|
||||
use pgp::{Deserializable, SignedPublicKey, StandaloneSignature};
|
||||
use tracing::debug;
|
||||
|
||||
/// Verify a pgp signature for some message given some public keys
|
||||
pub fn verify_sig(sig: &str, msg: &[u8], keys: &[SignedPublicKey]) -> anyhow::Result<usize> {
|
||||
let (signatures, _) =
|
||||
StandaloneSignature::from_string_many(sig).context(format!("reading signature {}", sig))?;
|
||||
|
||||
for signature in signatures {
|
||||
let signature = match signature {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
debug!("Failed to parse signature: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
for (pos, key) in keys.iter().enumerate() {
|
||||
let actual_key = &key.primary_key;
|
||||
if actual_key.is_signing_key() && signature.verify(&actual_key, msg).is_ok() {
|
||||
return Ok(pos);
|
||||
}
|
||||
for sub_key in &key.public_subkeys {
|
||||
if sub_key.is_signing_key() && signature.verify(sub_key, msg).is_ok() {
|
||||
return Ok(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("Failed to verify signature for `{sig}`");
|
||||
bail!("Failed to verify signature for `{sig}`");
|
||||
}
|
||||
|
||||
/// Verify pgp signatures for a message with some threshold
|
||||
pub fn check_sigs(
|
||||
pgp_keys: &[Box<[u8]>],
|
||||
threshold: usize,
|
||||
signatures: &[String],
|
||||
msg: &[u8],
|
||||
) -> Result<(), HttpResponseError> {
|
||||
let mut keys = Vec::new();
|
||||
|
||||
for bytes in pgp_keys {
|
||||
let key = SignedPublicKey::from_bytes(bytes.as_ref())
|
||||
.context("parsing public key")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
let mut verified: usize = 0;
|
||||
|
||||
for sig in signatures {
|
||||
if let Ok(pos) = verify_sig(sig, msg, &keys) {
|
||||
keys.remove(pos);
|
||||
verified += 1;
|
||||
}
|
||||
if verified >= threshold {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if verified < threshold {
|
||||
return Err(anyhow!("not enough valid signatures")).status(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify pgp signatures for a message
|
||||
pub trait VerifySig {
|
||||
/// Verify pgp signatures for a message
|
||||
fn check_sigs(&self, signatures: &[String], msg: &[u8]) -> Result<(), HttpResponseError>;
|
||||
}
|
||||
|
||||
impl VerifySig for AdminConfig {
|
||||
fn check_sigs(&self, signatures: &[String], msg: &[u8]) -> Result<(), HttpResponseError> {
|
||||
check_sigs(&self.admin_pgp_keys, self.admin_threshold, signatures, msg)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::verify_sig;
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use pgp::{Deserializable, SignedPublicKey};
|
||||
|
||||
const TEST_DATA: &str = include_str!("../../tests/data/test.json");
|
||||
|
||||
// gpg --armor --local-user test@example.com --detach-sign bin/tee-vault-admin/tests/data/test.json
|
||||
const TEST_SIG: &str = include_str!("../../tests/data/test.json.asc");
|
||||
|
||||
// gpg --armor --export 81A312C59D679D930FA9E8B06D728F29A2DBABF8 > bin/tee-vault-admin/tests/data/pub-81A312C59D679D930FA9E8B06D728F29A2DBABF8.asc
|
||||
const TEST_KEY: &str =
|
||||
include_str!("../../tests/data/pub-81A312C59D679D930FA9E8B06D728F29A2DBABF8.asc");
|
||||
|
||||
const TEST_KEY_BASE64: &str =
|
||||
include_str!("../../tests/data/pub-81A312C59D679D930FA9E8B06D728F29A2DBABF8.b64");
|
||||
|
||||
#[test]
|
||||
fn test_sig() {
|
||||
let test_key = SignedPublicKey::from_string(TEST_KEY).unwrap().0;
|
||||
verify_sig(TEST_SIG, TEST_DATA.as_bytes(), &[test_key]).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_import() {
|
||||
let str = TEST_KEY_BASE64.lines().collect::<String>();
|
||||
let bytes = general_purpose::STANDARD.decode(str).unwrap();
|
||||
let _ = SignedPublicKey::from_bytes(bytes.as_slice()).unwrap();
|
||||
}
|
||||
}
|
50
crates/teepot/src/sgx/error.rs
Normal file
50
crates/teepot/src/sgx/error.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
|
||||
//! Intel SGX Enclave error wrapper
|
||||
|
||||
use bytemuck::PodCastError;
|
||||
use intel_tee_quote_verification_rs::quote3_error_t;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
/// Wrapper for the quote verification Error
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Quote3Error {
|
||||
/// error message
|
||||
pub msg: &'static str,
|
||||
/// raw error code
|
||||
pub inner: quote3_error_t,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Quote3Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}: {:?}", self.msg, self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Quote3Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}: {:?}", self.msg, self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Quote3Error {}
|
||||
|
||||
impl From<quote3_error_t> for Quote3Error {
|
||||
fn from(inner: quote3_error_t) -> Self {
|
||||
Self {
|
||||
msg: "Generic",
|
||||
inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum QuoteFromError {
|
||||
#[error(transparent)]
|
||||
PodCastError(#[from] PodCastError),
|
||||
|
||||
#[error("Quote version is invalid")]
|
||||
InvalidVersion,
|
||||
}
|
249
crates/teepot/src/sgx/mod.rs
Normal file
249
crates/teepot/src/sgx/mod.rs
Normal file
|
@ -0,0 +1,249 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
// Copyright (c) The Enarx Project Developers https://github.com/enarx/sgx
|
||||
|
||||
//! Intel SGX Enclave report structures.
|
||||
|
||||
pub mod error;
|
||||
pub mod sign;
|
||||
pub mod tcblevel;
|
||||
|
||||
use bytemuck::{cast_slice, try_from_bytes, AnyBitPattern, PodCastError};
|
||||
use intel_tee_quote_verification_rs::{
|
||||
quote3_error_t, sgx_ql_qv_supplemental_t, tee_get_supplemental_data_version_and_size,
|
||||
tee_supp_data_descriptor_t, tee_verify_quote,
|
||||
};
|
||||
use std::ffi::CStr;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Write};
|
||||
use std::mem;
|
||||
use tracing::{trace, warn};
|
||||
|
||||
use crate::quote::GetQuoteError;
|
||||
pub use error::{Quote3Error, QuoteFromError};
|
||||
pub use intel_tee_quote_verification_rs::{sgx_ql_qv_result_t, Collateral};
|
||||
pub use tcblevel::{parse_tcb_levels, EnumSet, TcbLevel};
|
||||
|
||||
/// Structure of a quote
|
||||
#[derive(Copy, Clone, Debug, AnyBitPattern)]
|
||||
#[repr(C)]
|
||||
pub struct Quote {
|
||||
version: [u8; 2],
|
||||
key_type: [u8; 2],
|
||||
reserved: [u8; 4],
|
||||
qe_svn: [u8; 2],
|
||||
pce_svn: [u8; 2],
|
||||
qe_vendor_id: [u8; 16],
|
||||
/// The user data that was passed, when creating the enclave
|
||||
pub user_data: [u8; 20],
|
||||
/// The report body
|
||||
pub report_body: ReportBody,
|
||||
}
|
||||
|
||||
impl Quote {
|
||||
/// Creates a quote from a byte slice
|
||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<&Self, QuoteFromError> {
|
||||
if bytes.len() < mem::size_of::<Self>() {
|
||||
return Err(PodCastError::SizeMismatch.into());
|
||||
}
|
||||
let this: &Self = try_from_bytes(&bytes[..mem::size_of::<Self>()])?;
|
||||
if this.version() != 3 {
|
||||
return Err(QuoteFromError::InvalidVersion);
|
||||
}
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
/// Version of the `Quote` structure
|
||||
pub fn version(&self) -> u16 {
|
||||
u16::from_le_bytes(self.version)
|
||||
}
|
||||
}
|
||||
|
||||
/// The enclave report body.
|
||||
///
|
||||
/// For more information see the following documents:
|
||||
///
|
||||
/// [Intel® Software Guard Extensions (Intel® SGX) Data Center Attestation Primitives: ECDSA Quote Library API](https://download.01.org/intel-sgx/dcap-1.0/docs/SGX_ECDSA_QuoteGenReference_DCAP_API_Linux_1.0.pdf)
|
||||
///
|
||||
/// Table 5, A.4. Quote Format
|
||||
///
|
||||
/// [Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 3 (3A, 3B, 3C & 3D): System Programming Guide](https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3d-part-4-manual.html)
|
||||
///
|
||||
/// Table 38-21. Layout of REPORT
|
||||
#[derive(Copy, Clone, Debug, AnyBitPattern)]
|
||||
#[repr(C)]
|
||||
pub struct ReportBody {
|
||||
/// The security version number of the enclave.
|
||||
pub cpusvn: [u8; 16],
|
||||
/// The Misc section of the StateSaveArea of the enclave
|
||||
pub miscselect: [u8; 4],
|
||||
reserved1: [u8; 28],
|
||||
/// The allowed Features of the enclave.
|
||||
pub features: [u8; 8],
|
||||
/// The allowed XCr0Flags of the enclave.
|
||||
pub xfrm: [u8; 8],
|
||||
/// The measurement of the enclave
|
||||
pub mrenclave: [u8; 32],
|
||||
reserved2: [u8; 32],
|
||||
/// The hash of the public key, that signed the enclave
|
||||
pub mrsigner: [u8; 32],
|
||||
reserved3: [u8; 96],
|
||||
/// ISV assigned Product ID of the enclave.
|
||||
pub isv_prodid: [u8; 2],
|
||||
/// ISV assigned SVN (security version number) of the enclave.
|
||||
pub isv_svn: [u8; 2],
|
||||
reserved4: [u8; 60],
|
||||
/// The enclave report data, injected when requesting the quote, that is used for attestation.
|
||||
pub reportdata: [u8; 64],
|
||||
}
|
||||
|
||||
/// The result of the quote verification
|
||||
pub struct QuoteVerificationResult<'a> {
|
||||
/// the raw result
|
||||
pub result: sgx_ql_qv_result_t,
|
||||
/// indicates if the collateral is expired
|
||||
pub collateral_expired: bool,
|
||||
/// the earliest expiration date of the collateral
|
||||
pub earliest_expiration_date: i64,
|
||||
/// Date of the TCB level
|
||||
pub tcb_level_date_tag: i64,
|
||||
/// the advisory string
|
||||
pub advisories: Vec<String>,
|
||||
/// the quote
|
||||
pub quote: &'a Quote,
|
||||
}
|
||||
|
||||
/// Verifies a quote with optional collateral material
|
||||
pub fn verify_quote_with_collateral<'a>(
|
||||
quote: &'a [u8],
|
||||
collateral: Option<&Collateral>,
|
||||
current_time: i64,
|
||||
) -> Result<QuoteVerificationResult<'a>, Quote3Error> {
|
||||
let mut supp_data: mem::MaybeUninit<sgx_ql_qv_supplemental_t> = mem::MaybeUninit::zeroed();
|
||||
let mut supp_data_desc = tee_supp_data_descriptor_t {
|
||||
major_version: 0,
|
||||
data_size: 0,
|
||||
p_data: supp_data.as_mut_ptr() as *mut u8,
|
||||
};
|
||||
trace!("tee_get_supplemental_data_version_and_size");
|
||||
let (_, supp_size) =
|
||||
tee_get_supplemental_data_version_and_size(quote).map_err(|e| Quote3Error {
|
||||
msg: "tee_get_supplemental_data_version_and_size",
|
||||
inner: e,
|
||||
})?;
|
||||
|
||||
trace!(
|
||||
"tee_get_supplemental_data_version_and_size supp_size: {}",
|
||||
supp_size
|
||||
);
|
||||
|
||||
if supp_size == mem::size_of::<sgx_ql_qv_supplemental_t>() as u32 {
|
||||
supp_data_desc.data_size = supp_size;
|
||||
} else {
|
||||
supp_data_desc.data_size = 0;
|
||||
trace!(
|
||||
"tee_get_supplemental_data_version_and_size supp_size: {}",
|
||||
supp_size
|
||||
);
|
||||
trace!(
|
||||
"mem::size_of::<sgx_ql_qv_supplemental_t>(): {}",
|
||||
mem::size_of::<sgx_ql_qv_supplemental_t>()
|
||||
);
|
||||
warn!("Quote supplemental data size is different between DCAP QVL and QvE, please make sure you installed DCAP QVL and QvE from same release.")
|
||||
}
|
||||
|
||||
let p_supplemental_data = match supp_data_desc.data_size {
|
||||
0 => None,
|
||||
_ => Some(&mut supp_data_desc),
|
||||
};
|
||||
|
||||
let has_sup = p_supplemental_data.is_some();
|
||||
|
||||
trace!("tee_verify_quote");
|
||||
|
||||
let (collateral_expiration_status, result) =
|
||||
tee_verify_quote(quote, collateral, current_time, None, p_supplemental_data).map_err(
|
||||
|e| Quote3Error {
|
||||
msg: "tee_verify_quote",
|
||||
inner: e,
|
||||
},
|
||||
)?;
|
||||
|
||||
// check supplemental data if necessary
|
||||
let (advisories, earliest_expiration_date, tcb_level_date_tag) = if has_sup {
|
||||
unsafe {
|
||||
let supp_data = supp_data.assume_init();
|
||||
// convert to valid UTF-8 string
|
||||
let ads = CStr::from_bytes_until_nul(cast_slice(&supp_data.sa_list[..]))
|
||||
.ok()
|
||||
.and_then(|s| CStr::to_str(s).ok())
|
||||
.into_iter()
|
||||
.flat_map(|s| s.split(',').map(str::trim).map(String::from))
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
(
|
||||
ads,
|
||||
supp_data.earliest_expiration_date,
|
||||
supp_data.tcb_level_date_tag,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(vec![], 0, 0)
|
||||
};
|
||||
|
||||
let quote = Quote::try_from_bytes(quote).map_err(|_| Quote3Error {
|
||||
msg: "Quote::try_from_bytes",
|
||||
inner: quote3_error_t::SGX_QL_QUOTE_FORMAT_UNSUPPORTED,
|
||||
})?;
|
||||
|
||||
let res = QuoteVerificationResult {
|
||||
collateral_expired: collateral_expiration_status != 0,
|
||||
earliest_expiration_date,
|
||||
tcb_level_date_tag,
|
||||
result,
|
||||
quote,
|
||||
advisories,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get the attestation report in a Gramine enclave
|
||||
pub fn sgx_gramine_get_quote(report_data: &[u8; 64]) -> Result<Box<[u8]>, GetQuoteError> {
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.open("/dev/attestation/user_report_data")
|
||||
.map_err(|e| GetQuoteError {
|
||||
msg: "Failed to open `/dev/attestation/user_report_data`".into(),
|
||||
source: e,
|
||||
})?;
|
||||
|
||||
file.write(report_data).map_err(|e| GetQuoteError {
|
||||
msg: "Failed to write `/dev/attestation/user_report_data`".into(),
|
||||
source: e,
|
||||
})?;
|
||||
|
||||
drop(file);
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open("/dev/attestation/quote")
|
||||
.map_err(|e| GetQuoteError {
|
||||
msg: "Failed to open `/dev/attestation/quote`".into(),
|
||||
source: e,
|
||||
})?;
|
||||
|
||||
let mut quote = Vec::new();
|
||||
file.read_to_end(&mut quote).map_err(|e| GetQuoteError {
|
||||
msg: "Failed to read `/dev/attestation/quote`".into(),
|
||||
source: e,
|
||||
})?;
|
||||
Ok(quote.into_boxed_slice())
|
||||
}
|
||||
|
||||
/// Wrapper func for error
|
||||
/// TODO: move to intel_tee_quote_verification_rs
|
||||
pub fn tee_qv_get_collateral(quote: &[u8]) -> Result<Collateral, Quote3Error> {
|
||||
intel_tee_quote_verification_rs::tee_qv_get_collateral(quote).map_err(Into::into)
|
||||
}
|
384
crates/teepot/src/sgx/sign.rs
Normal file
384
crates/teepot/src/sgx/sign.rs
Normal file
|
@ -0,0 +1,384 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
// Copyright (c) The Enarx Project Developers https://github.com/enarx/sgx
|
||||
|
||||
//! SGX signature structures
|
||||
//!
|
||||
//! Mostly copied from the [`sgx`](https://crates.io/crates/sgx) crate,
|
||||
//! but with some modifications to make it easier to use in a standalone context.
|
||||
//!
|
||||
|
||||
use bytemuck::{bytes_of, Pod, Zeroable};
|
||||
use num_integer::Integer;
|
||||
use num_traits::ToPrimitive;
|
||||
use rand::thread_rng;
|
||||
use rsa::{
|
||||
pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey, LineEnding},
|
||||
traits::PublicKeyParts,
|
||||
BigUint, Pkcs1v15Sign, RsaPrivateKey,
|
||||
};
|
||||
use sha2::Digest as _;
|
||||
use sha2::Sha256;
|
||||
pub use zeroize::Zeroizing;
|
||||
|
||||
/// Enclave CPU attributes
|
||||
///
|
||||
/// This type represents the CPU features turned on in an enclave.
|
||||
#[repr(C, packed(4))]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
pub struct Attributes {
|
||||
features: u64,
|
||||
xfrm: u64,
|
||||
}
|
||||
|
||||
/// The `Author` of an enclave
|
||||
///
|
||||
/// This structure encompasses the first block of fields from `SIGSTRUCT`
|
||||
/// that is included in the signature. It is split out from `Signature`
|
||||
/// in order to make it easy to hash the fields for the signature.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Pod)]
|
||||
pub struct Author {
|
||||
header1: [u8; 16],
|
||||
vendor: u32,
|
||||
date: u32,
|
||||
header2: [u8; 16],
|
||||
swdefined: u32,
|
||||
reserved: [u32; 21],
|
||||
}
|
||||
|
||||
unsafe impl Zeroable for Author {}
|
||||
|
||||
impl Author {
|
||||
const HEADER1: [u8; 16] = 0x06000000E10000000000010000000000u128.to_be_bytes();
|
||||
const HEADER2: [u8; 16] = 0x01010000600000006000000001000000u128.to_be_bytes();
|
||||
|
||||
#[allow(clippy::unreadable_literal)]
|
||||
/// Creates a new Author from a date and software defined value.
|
||||
///
|
||||
/// Note that the `date` input is defined in binary-coded decimal. For
|
||||
/// example, the unix epoch is: `0x1970_01_01`.
|
||||
pub const fn new(date: u32, swdefined: u32) -> Self {
|
||||
Self {
|
||||
header1: Self::HEADER1,
|
||||
vendor: 0,
|
||||
date,
|
||||
header2: Self::HEADER2,
|
||||
swdefined,
|
||||
reserved: [0; 21],
|
||||
}
|
||||
}
|
||||
|
||||
/// get the date
|
||||
#[inline]
|
||||
pub fn date(&self) -> u32 {
|
||||
self.date
|
||||
}
|
||||
|
||||
/// get the swdefined
|
||||
#[inline]
|
||||
pub fn swdefined(&self) -> u32 {
|
||||
self.swdefined
|
||||
}
|
||||
}
|
||||
|
||||
/// The enclave signature body
|
||||
///
|
||||
/// This structure encompasses the second block of fields from `SIGSTRUCT`
|
||||
/// that is included in the signature. It is split out from `Signature`
|
||||
/// in order to make it easy to hash the fields for the signature.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Pod, Zeroable)]
|
||||
pub struct Body {
|
||||
misc_select: u32,
|
||||
misc_mask: u32,
|
||||
cet_attr_select: u8,
|
||||
cet_attr_mask: u8,
|
||||
reserved0: [u8; 2],
|
||||
ext_fid: [u8; 16],
|
||||
attr_select: [u8; 16],
|
||||
attr_mask: [u8; 16],
|
||||
mrenclave: [u8; 32],
|
||||
reserved1: [u8; 16],
|
||||
ext_pid: [u8; 16],
|
||||
pid: u16,
|
||||
svn: u16,
|
||||
}
|
||||
|
||||
impl Body {
|
||||
/// Check if the debug flag can be set
|
||||
#[inline]
|
||||
pub fn can_set_debug(&self) -> bool {
|
||||
/// Enables enclave debug mode
|
||||
///
|
||||
/// This gives permission to use EDBGRD and EDBGWR to read and write
|
||||
/// enclave memory as plaintext, respectively. You most likely want
|
||||
/// to validate that this option is disabled during attestion.
|
||||
const DEBUG: u64 = 1 << 1;
|
||||
let attr_select: &Attributes = bytemuck::try_from_bytes(&self.attr_select).unwrap();
|
||||
let attr_mask: &Attributes = bytemuck::try_from_bytes(&self.attr_mask).unwrap();
|
||||
|
||||
(attr_select.features & DEBUG) == 0 && (attr_mask.features & DEBUG) == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// A signature on an enclave
|
||||
///
|
||||
/// This structure encompasses the `SIGSTRUCT` structure from the SGX
|
||||
/// documentation, renamed for ergonomics. The two portions of the
|
||||
/// data that are included in the signature are further divided into
|
||||
/// subordinate structures (`Author` and `Body`) for ease during
|
||||
/// signature generation and validation.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Pod, Zeroable)]
|
||||
pub struct Signature {
|
||||
author: Author,
|
||||
modulus: [u8; 384],
|
||||
exponent: u32,
|
||||
signature: [u8; 384],
|
||||
body: Body,
|
||||
reserved: [u8; 12],
|
||||
q1: [u8; 384],
|
||||
q2: [u8; 384],
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
/// Signs the supplied `author` and `body` with the specified `key`.
|
||||
pub fn new<T: PrivateKey>(key: &T, author: Author, body: Body) -> Result<Self, T::Error> {
|
||||
let a = bytes_of(&author);
|
||||
let b = bytes_of(&body);
|
||||
let sd = key.sign(a, b)?;
|
||||
|
||||
Ok(Self {
|
||||
author,
|
||||
modulus: sd.modulus,
|
||||
exponent: sd.exponent,
|
||||
signature: sd.signature,
|
||||
body,
|
||||
reserved: [0; 12],
|
||||
q1: sd.q1,
|
||||
q2: sd.q2,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the author of the enclave.
|
||||
pub fn author(&self) -> Author {
|
||||
self.author
|
||||
}
|
||||
|
||||
/// Returns the body of the enclave.
|
||||
pub fn body(&self) -> Body {
|
||||
self.body
|
||||
}
|
||||
}
|
||||
|
||||
/// A detached enclave signature
|
||||
pub struct SigData {
|
||||
/// The signature
|
||||
pub signature: [u8; 384],
|
||||
/// The public modulus
|
||||
pub modulus: [u8; 384],
|
||||
/// The public exponent
|
||||
pub exponent: u32,
|
||||
/// The first prime factor
|
||||
pub q1: [u8; 384],
|
||||
/// The second prime factor
|
||||
pub q2: [u8; 384],
|
||||
}
|
||||
|
||||
/// A fixed-size hash
|
||||
pub trait Digest: Sized {
|
||||
/// The output size of the hash
|
||||
type Output: AsRef<[u8]>;
|
||||
|
||||
/// Construct a new hasher
|
||||
fn new() -> Self;
|
||||
/// Update the hasher with more data
|
||||
fn update(&mut self, bytes: &[u8]);
|
||||
/// Finalize the hasher and return the digest
|
||||
fn finish(self) -> Self::Output;
|
||||
|
||||
/// Update the hasher with more data and return the hasher
|
||||
#[inline]
|
||||
fn chain(mut self, bytes: &[u8]) -> Self {
|
||||
self.update(bytes);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A private key used for signing an enclave
|
||||
pub trait PrivateKey: Sized {
|
||||
/// The error type for this key
|
||||
type Error: core::fmt::Debug;
|
||||
|
||||
/// Generate a new private key
|
||||
fn generate(exponent: u8) -> Result<Self, Self::Error>;
|
||||
|
||||
/// Stringify the private key as a PEM-encoded string
|
||||
fn to_pem(&self) -> Result<Zeroizing<String>, Self::Error>;
|
||||
|
||||
/// Load a private key from a PEM-encoded string
|
||||
fn from_pem(pem: &str) -> Result<Self, Self::Error>;
|
||||
/// Load a private key from a DER-encoded buffer
|
||||
fn from_der(der: &[u8]) -> Result<Self, Self::Error>;
|
||||
/// Sign the specified `author` and `body`
|
||||
fn sign(&self, author: &[u8], body: &[u8]) -> Result<SigData, Self::Error>;
|
||||
}
|
||||
|
||||
fn arr_from_big(value: &BigUint) -> [u8; 384] {
|
||||
let mut arr = [0u8; 384];
|
||||
let buf = value.to_bytes_le();
|
||||
arr[..buf.len()].copy_from_slice(&buf);
|
||||
arr
|
||||
}
|
||||
|
||||
/// SHA2-256
|
||||
pub struct S256Digest(Sha256);
|
||||
|
||||
impl Digest for S256Digest {
|
||||
type Output = [u8; 32];
|
||||
|
||||
#[inline]
|
||||
fn new() -> Self {
|
||||
Self(Sha256::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update(&mut self, bytes: &[u8]) {
|
||||
self.0.update(bytes)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn finish(self) -> Self::Output {
|
||||
*self.0.finalize().as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// RSA w/ SHA2-256
|
||||
pub struct RS256PrivateKey(RsaPrivateKey);
|
||||
|
||||
impl RS256PrivateKey {
|
||||
/// Create a new RSA private key.
|
||||
pub fn new(key: RsaPrivateKey) -> Self {
|
||||
assert!(key.n().bits() <= 384 * 8);
|
||||
Self(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrivateKey for RS256PrivateKey {
|
||||
type Error = rsa::errors::Error;
|
||||
|
||||
fn generate(exponent: u8) -> Result<Self, Self::Error> {
|
||||
let mut rng = thread_rng();
|
||||
let exp = BigUint::from(exponent);
|
||||
let key = RsaPrivateKey::new_with_exp(&mut rng, 384 * 8, &exp)?;
|
||||
Ok(Self::new(key))
|
||||
}
|
||||
|
||||
fn to_pem(&self) -> Result<Zeroizing<String>, Self::Error> {
|
||||
let pem = RsaPrivateKey::to_pkcs1_pem(&self.0, LineEnding::default())?;
|
||||
Ok(pem)
|
||||
}
|
||||
|
||||
fn from_pem(pem: &str) -> Result<Self, Self::Error> {
|
||||
let key = RsaPrivateKey::from_pkcs1_pem(pem)?;
|
||||
Ok(Self::new(key))
|
||||
}
|
||||
|
||||
fn from_der(der: &[u8]) -> Result<Self, Self::Error> {
|
||||
let key = RsaPrivateKey::from_pkcs1_der(der)?;
|
||||
Ok(Self::new(key))
|
||||
}
|
||||
|
||||
fn sign(&self, author: &[u8], body: &[u8]) -> Result<SigData, Self::Error> {
|
||||
use sha2::digest::Update;
|
||||
|
||||
let hash = Sha256::new().chain(author).chain(body).finalize();
|
||||
|
||||
let padding = Pkcs1v15Sign::new::<Sha256>();
|
||||
let sig = self.0.sign(padding, &hash)?;
|
||||
|
||||
// Calculate q1 and q2.
|
||||
let s = BigUint::from_bytes_be(&sig);
|
||||
let m = self.0.n();
|
||||
let (q1, qr) = (&s * &s).div_rem(m);
|
||||
let q2 = (&s * qr) / m;
|
||||
|
||||
Ok(SigData {
|
||||
signature: arr_from_big(&s),
|
||||
modulus: arr_from_big(m),
|
||||
exponent: self.0.e().to_u32().unwrap(),
|
||||
q1: arr_from_big(&q1),
|
||||
q2: arr_from_big(&q2),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Author, Body, Signature};
|
||||
use testaso::testaso;
|
||||
|
||||
testaso! {
|
||||
struct Author: 4, 128 => {
|
||||
header1: 0,
|
||||
vendor: 16,
|
||||
date: 20,
|
||||
header2: 24,
|
||||
swdefined: 40,
|
||||
reserved: 44
|
||||
}
|
||||
struct Body: 4, 128 => {
|
||||
misc_select: 0,
|
||||
misc_mask: 4,
|
||||
cet_attr_select: 8,
|
||||
cet_attr_mask: 9,
|
||||
reserved0: 10,
|
||||
ext_fid: 12,
|
||||
attr_select: 28,
|
||||
attr_mask: 44,
|
||||
mrenclave: 60,
|
||||
reserved1: 92,
|
||||
ext_pid: 108,
|
||||
pid: 124,
|
||||
svn: 126
|
||||
}
|
||||
struct Signature: 4, 1808 => {
|
||||
author: 0,
|
||||
modulus: 128,
|
||||
exponent: 512,
|
||||
signature: 516,
|
||||
body: 900,
|
||||
reserved: 1028,
|
||||
q1: 1040,
|
||||
q2: 1424
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::unusual_byte_groupings)]
|
||||
fn author_instantiation() {
|
||||
let author = Author::new(0x2000_03_30, 0u32);
|
||||
assert_eq!(author.header1, Author::HEADER1);
|
||||
assert_eq!(author.vendor, 0u32);
|
||||
assert_eq!(author.date, 0x2000_03_30);
|
||||
assert_eq!(author.header2, Author::HEADER2);
|
||||
assert_eq!(author.swdefined, 0u32);
|
||||
assert_eq!(author.reserved, [0; 21]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signature() {
|
||||
let test_sig = include_bytes!("../../tests/data/gramine-test.sig");
|
||||
let sig: Signature = bytemuck::try_pod_read_unaligned(test_sig).unwrap();
|
||||
let body = sig.body();
|
||||
assert_eq!(
|
||||
body.mrenclave.to_vec(),
|
||||
hex::decode("f78170fe28e2e8671d83f5056975d25a27eb2c333dc520c2ccaf4de6b3f9c81b")
|
||||
.unwrap()
|
||||
);
|
||||
assert!(!body.can_set_debug());
|
||||
assert_eq!(body.misc_select, 0);
|
||||
}
|
||||
}
|
93
crates/teepot/src/sgx/tcblevel.rs
Normal file
93
crates/teepot/src/sgx/tcblevel.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
|
||||
//! Intel SGX Enclave TCB level wrapper
|
||||
|
||||
use enumset::EnumSetType;
|
||||
use intel_tee_quote_verification_rs::sgx_ql_qv_result_t;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub use enumset::EnumSet;
|
||||
|
||||
/// TCB level
|
||||
#[derive(EnumSetType, Debug)]
|
||||
pub enum TcbLevel {
|
||||
/// TCB is up to date
|
||||
Ok,
|
||||
/// TCB is up to date, but the configuration is not
|
||||
ConfigNeeded,
|
||||
/// TCB is up to date, but the configuration and software hardening is not
|
||||
ConfigAndSwHardeningNeeded,
|
||||
/// TCB is up to date, but the software hardening is not
|
||||
SwHardeningNeeded,
|
||||
/// TCB is out of date
|
||||
OutOfDate,
|
||||
/// TCB is out of date and the configuration is also out of date
|
||||
OutOfDateConfigNeeded,
|
||||
/// TCB level is invalid
|
||||
Invalid,
|
||||
}
|
||||
|
||||
impl From<sgx_ql_qv_result_t> for TcbLevel {
|
||||
fn from(value: sgx_ql_qv_result_t) -> Self {
|
||||
match value {
|
||||
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK => TcbLevel::Ok,
|
||||
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE => TcbLevel::OutOfDate,
|
||||
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE_CONFIG_NEEDED => {
|
||||
TcbLevel::OutOfDateConfigNeeded
|
||||
}
|
||||
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_SW_HARDENING_NEEDED => TcbLevel::SwHardeningNeeded,
|
||||
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_CONFIG_AND_SW_HARDENING_NEEDED => {
|
||||
TcbLevel::ConfigAndSwHardeningNeeded
|
||||
}
|
||||
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_CONFIG_NEEDED => TcbLevel::ConfigNeeded,
|
||||
_ => TcbLevel::Invalid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TcbLevel {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"Ok" => Ok(TcbLevel::Ok),
|
||||
"ConfigNeeded" => Ok(TcbLevel::ConfigNeeded),
|
||||
"ConfigAndSwHardeningNeeded" => Ok(TcbLevel::ConfigAndSwHardeningNeeded),
|
||||
"SwHardeningNeeded" => Ok(TcbLevel::SwHardeningNeeded),
|
||||
"OutOfDate" => Ok(TcbLevel::OutOfDate),
|
||||
"OutOfDateConfigNeeded" => Ok(TcbLevel::OutOfDateConfigNeeded),
|
||||
"Invalid" => Ok(TcbLevel::Invalid),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TcbLevel {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TcbLevel::Ok => write!(f, "Ok"),
|
||||
TcbLevel::ConfigNeeded => write!(f, "ConfigNeeded: Firmware needs to be updated"),
|
||||
TcbLevel::ConfigAndSwHardeningNeeded => write!(f, "ConfigAndSwHardeningNeeded: Firmware configuration needs to be updated and software hardening is needed"),
|
||||
TcbLevel::SwHardeningNeeded => write!(f, "SwHardeningNeeded: Software hardening is needed"),
|
||||
TcbLevel::OutOfDate => write!(f, "OutOfDate: Firmware needs to be updated"),
|
||||
TcbLevel::OutOfDateConfigNeeded => write!(f, "OutOfDateConfigNeeded: Firmware needs to be updated and configuration needs to be updated."),
|
||||
TcbLevel::Invalid => write!(f, "Invalid TCB level"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a comma-separated list of TCB levels
|
||||
pub fn parse_tcb_levels(
|
||||
s: &str,
|
||||
) -> Result<EnumSet<TcbLevel>, Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
let mut set = EnumSet::new();
|
||||
for level_str in s.split(',') {
|
||||
let level_str = level_str.trim();
|
||||
let level = TcbLevel::from_str(level_str)
|
||||
.map_err(|_| format!("Invalid TCB level: {}", level_str))?;
|
||||
set.insert(level);
|
||||
}
|
||||
Ok(set)
|
||||
}
|
83
crates/teepot/tests/data/gpgkey.asc
Normal file
83
crates/teepot/tests/data/gpgkey.asc
Normal file
|
@ -0,0 +1,83 @@
|
|||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
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
|
||||
NcPkByuqaJvurs3VRpU4rOyFaxaTKXfr
|
||||
=Bpm9
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
31
crates/teepot/tests/data/gpgkey.pub
Normal file
31
crates/teepot/tests/data/gpgkey.pub
Normal file
|
@ -0,0 +1,31 @@
|
|||
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
|
||||
NcPkByuqaJvurs3VRpU4rOyFaxaTKXfr
|
BIN
crates/teepot/tests/data/gramine-test.sig
Normal file
BIN
crates/teepot/tests/data/gramine-test.sig
Normal file
Binary file not shown.
|
@ -0,0 +1,23 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQGNBGTBKKQBDACo28S5h7rvfcj89OIv66evSsOExJJ363oYWBfs0GPkgPG/bS2p
|
||||
gIFe5+yqvW5q2tnDYHz0WJkYQcwTqQQpt/Ma+UV+uaHc2pJfKvsmpI9o3xf+eV3C
|
||||
9HrU+6lwr8efkBkjLrfzkroQf0II/eX9omePt4qXNMX07UKI1ZrmXWmn5BlXiwkQ
|
||||
l0M9XWwS3PZ2aiM2MMUjfmDAdi5pc10UgZddlbInjK8guXj9/HQt23l3W1df83QN
|
||||
lxC+sDmlekwugeg0HckcgTjGICvML+gwsX/GVDMJe/O8D7Sy7KKrO23xwus7p8At
|
||||
4C5+0WCqARBKQbTE0IZTyUZUgt6xnn1BhFHBCblpI2KPkRXSUWBkb/Npr40Y2uMi
|
||||
BZNk2Iwdyuim8+Zs5pHCISvR09COswFgM+K7ikv7EMMr1YsEcFGH4sE3GQXVw4qO
|
||||
NdITNBAz/5+cPWAOUOske6BeUjLaC4XaG4wY2Rg7RVoDCqhKav+VYE2B/X0pNVSf
|
||||
PyZKirkMllHVwA8AEQEAAbQXdGVzdCA8dGVzdEBleGFtcGxlLmNvbT6JAdEEEwEI
|
||||
ADsWIQSBoxLFnWedkw+p6LBtco8potur+AUCZMEopAIbAwULCQgHAgIiAgYVCgkI
|
||||
CwIEFgIDAQIeBwIXgAAKCRBtco8potur+Cb2C/9sc2VYpi8eYnPrclU/28SoecG9
|
||||
cyPAZ5D+AQfAnVPLDdeBgLLr4FrMkRLvFNEoNwSFWAHNde77ChKFVPjqs1ubR+P6
|
||||
RJ8YjeuF5l5objn/xXd+fe79gjBfWRVUR36+q9TvJwr51bL/cFNv0eGpvxV4OhiX
|
||||
ImF99Ik3HtXX2w5b8rkH2DcW39HlQvxqOY3OTskmVn0nv5l6Q9CHrIXsaug11Bp/
|
||||
EQPyMG/E0Qa1SWCIyDwphEQzIUFIBg0zlAUC2Tlh9PYrgZJ5GjNY18a0ZglwTp48
|
||||
IpwOUXwT9IzOKyrM1jA9lg8P5ih/MLZKIMvroKrry+IZXQAujnvH0HwMehDuepR+
|
||||
liOx1byI2VitOkl7GpAZ03t+ae/KrsjJ+Dx/oFFkDVQAF2dDJHiFzZD8DHucIngY
|
||||
4b8Wq7N+8hzyk8AgKFK9rq3HqPH5L+NsJEzS1xOQeaGlI1H1knnAgRuIBZdZewrR
|
||||
/UosYei3mZtEclHU+YqeeBAsVoKKSVi2ryu8RCk=
|
||||
=LKzc
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
@ -0,0 +1,16 @@
|
|||
mQGNBGTBKKQBDACo28S5h7rvfcj89OIv66evSsOExJJ363oYWBfs0GPkgPG/bS2pgIFe5+yqvW5q
|
||||
2tnDYHz0WJkYQcwTqQQpt/Ma+UV+uaHc2pJfKvsmpI9o3xf+eV3C9HrU+6lwr8efkBkjLrfzkroQ
|
||||
f0II/eX9omePt4qXNMX07UKI1ZrmXWmn5BlXiwkQl0M9XWwS3PZ2aiM2MMUjfmDAdi5pc10UgZdd
|
||||
lbInjK8guXj9/HQt23l3W1df83QNlxC+sDmlekwugeg0HckcgTjGICvML+gwsX/GVDMJe/O8D7Sy
|
||||
7KKrO23xwus7p8At4C5+0WCqARBKQbTE0IZTyUZUgt6xnn1BhFHBCblpI2KPkRXSUWBkb/Npr40Y
|
||||
2uMiBZNk2Iwdyuim8+Zs5pHCISvR09COswFgM+K7ikv7EMMr1YsEcFGH4sE3GQXVw4qONdITNBAz
|
||||
/5+cPWAOUOske6BeUjLaC4XaG4wY2Rg7RVoDCqhKav+VYE2B/X0pNVSfPyZKirkMllHVwA8AEQEA
|
||||
AbQXdGVzdCA8dGVzdEBleGFtcGxlLmNvbT6JAdEEEwEIADsWIQSBoxLFnWedkw+p6LBtco8potur
|
||||
+AUCZMEopAIbAwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRBtco8potur+Cb2C/9sc2VY
|
||||
pi8eYnPrclU/28SoecG9cyPAZ5D+AQfAnVPLDdeBgLLr4FrMkRLvFNEoNwSFWAHNde77ChKFVPjq
|
||||
s1ubR+P6RJ8YjeuF5l5objn/xXd+fe79gjBfWRVUR36+q9TvJwr51bL/cFNv0eGpvxV4OhiXImF9
|
||||
9Ik3HtXX2w5b8rkH2DcW39HlQvxqOY3OTskmVn0nv5l6Q9CHrIXsaug11Bp/EQPyMG/E0Qa1SWCI
|
||||
yDwphEQzIUFIBg0zlAUC2Tlh9PYrgZJ5GjNY18a0ZglwTp48IpwOUXwT9IzOKyrM1jA9lg8P5ih/
|
||||
MLZKIMvroKrry+IZXQAujnvH0HwMehDuepR+liOx1byI2VitOkl7GpAZ03t+ae/KrsjJ+Dx/oFFk
|
||||
DVQAF2dDJHiFzZD8DHucIngY4b8Wq7N+8hzyk8AgKFK9rq3HqPH5L+NsJEzS1xOQeaGlI1H1knnA
|
||||
gRuIBZdZewrR/UosYei3mZtEclHU+YqeeBAsVoKKSVi2ryu8RCk=
|
22
crates/teepot/tests/data/stress.json
Normal file
22
crates/teepot/tests/data/stress.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"last_digest": "",
|
||||
"commands": [
|
||||
{
|
||||
"url": "/v1/sys/policies/acl/tee-stress",
|
||||
"data": {
|
||||
"policy": "path \"secret/data/tee/stress/*\" { capabilities = [\"create\", \"read\", \"update\", \"delete\", \"list\" ] }\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "/v1/auth/tee/tees/stress",
|
||||
"data": {
|
||||
"lease": "1000",
|
||||
"name": "stress",
|
||||
"sgx_allowed_tcb_levels": "Ok,SwHardeningNeeded",
|
||||
"sgx_mrsigner": "c5591a72b8b86e0d8814d6e8750e3efe66aea2d102b8ba2405365559b858697d",
|
||||
"token_policies": "tee-stress",
|
||||
"types": "sgx"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
26
crates/teepot/tests/data/stress.json.asc
Normal file
26
crates/teepot/tests/data/stress.json.asc
Normal file
|
@ -0,0 +1,26 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAABCAAdFiEEC0Pk344t4+guABNk9RmhFDs/vjIFAmUn5WkACgkQ9RmhFDs/
|
||||
vjK5kw/8Dl1XuMOfJ+mxshaRH4JexPmB/+c4x5JnaYS6GGJaJ/eOp+hSFchxnwfI
|
||||
OXRV3S+/kgaytfu5zVEepqyypX43LbV+eZ5s450xa3qI0fR/rd+LnJeJNqFoJVrZ
|
||||
M+xTGpkBPvPB3340ebyti5k49I+uhgO8c5Cd1FpM00dWN0qUUJMsFuOKNueFURlC
|
||||
Sk9HLDk63G2/ZDyBzT83vMpFUZbtJ46yJmS2++W1UfEt2GZvL6sc2wr7pwlb5EtF
|
||||
wEgLtaIAy749h1Lzilw6RTWVC3ShQvaddIFIh+XagnrKmk2D0gha2XprbvBSkpYf
|
||||
a/9tZKH/4U8wfONR3sJ83wlODwks6zdIibspk7868vY5Bm9Yr2N1cIIPGtnnypHE
|
||||
xPZI9QXY/zUSnTXgs5JyEZkea8j29v135zhuGINFPVWOa+frGYTIN/zZ3sjdhZMt
|
||||
+4rRL8SZbFbkCDc/zGdlJOcTygUbEJBiseNJ8GXgbWrXzY/WDZpxL1xdxjkPK4PA
|
||||
xtKyaPlBP1B1RyHkZGYDq86t9DzX2H/gkqBHJpuSavcx/7/Q/b6KGYdf/QFxo7kY
|
||||
S0jdVXRVem0ClxWtEVZU9Wu9QykcYQbj3AM7hk+9Khmq32w7b3bwOndYNAojwzP+
|
||||
9UEVOAXv2K8LSBbq6RXott5KMKDwowOu4hQCNsDuvmBYkr1Sy5OJAcUEAAEIAC8W
|
||||
IQSBoxLFnWedkw+p6LBtco8potur+AUCZSflbREcdGVzdEBleGFtcGxlLmNvbQAK
|
||||
CRBtco8potur+LwPC/wOjT27sE4D/4Cadg58lXlRE2qoFdtc8vfs+ioxS7UxQX4m
|
||||
ggY4P6lHq8u2TkY4jDe9FpA5S7LNGQJoQx2zrr3lGwonBwGkj4nRM60/uNSar+wd
|
||||
Wknke8IUiv8E8MzITy+gKdFHwu95ZZh9IXefiQ4Fq8UQurELAfVA/sNk+1ovGzsO
|
||||
/S4srkR4uejsAuk84PCA7dgNLYobcU/7SMH/ffgorqE6BOXwzfIy13c9TV5ZztWo
|
||||
eK6R+wc92hza0ZvXVmB4i5NBe+aO7gSLe0QcJqHdaTpkcVhhhE+v8HdpF1JIgOH8
|
||||
/336W/ZOp1q1K0hL2rNU2YX40MOaZZLoxjfXNmC/dAZPel5HJMwTLzM6Aqqk49sB
|
||||
LHEPgHjefUWiHe2C31PGM0THM3fuA6i5OwypnZRI14WYVDlVa5KRmj/titcCt6aQ
|
||||
+fbzK5lYIg4AhLl8rIns8+/yJnwTIw3Zy94H8Xwjq8tplk6nSUWm0GKjYqFquwUf
|
||||
6PyKGVnqs2Cp0hFmD9o=
|
||||
=AsWI
|
||||
-----END PGP SIGNATURE-----
|
16
crates/teepot/tests/data/test.json
Normal file
16
crates/teepot/tests/data/test.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"last_digest": "",
|
||||
"commands": [
|
||||
{
|
||||
"url": "/v1/auth/tee/tees/test",
|
||||
"data": {
|
||||
"lease": "1000",
|
||||
"name": "test",
|
||||
"sgx_allowed_tcb_levels": "Ok,SwHardeningNeeded",
|
||||
"sgx_mrsigner": "c5591a72b8b86e0d8814d6e8750e3efe66aea2d102b8ba2405365559b858697d",
|
||||
"token_policies": "test",
|
||||
"types": "sgx"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
26
crates/teepot/tests/data/test.json.asc
Normal file
26
crates/teepot/tests/data/test.json.asc
Normal file
|
@ -0,0 +1,26 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAABCAAdFiEEC0Pk344t4+guABNk9RmhFDs/vjIFAmT5sBwACgkQ9RmhFDs/
|
||||
vjIJ2g/9E8kRdvz8hhxOyJRPpNZ9bcGJ+FvMjG7geEdixqu1Bwpfyj+UhGRY7Dgq
|
||||
4T46w/Hmr8YBZTI3xpafUSldyEvZnFXRuIQoBR+JQYNu8s9Jm9yDIyLLA86quiY8
|
||||
nU+x6x89sSOOvmTpRUBi6htTC4h0zZHHfAcmu0YS2pMmxXYJX7Rw3T7AJE0Uc4O/
|
||||
+0Ho1PMFB4BRmnNGlqBFc2u/yXEy38AWrmcheoPNtbdWmI/3leKikW5l2cXfPRuT
|
||||
tazc92NiIK6qu209jlusMBpADdu8FSAI9ax4dKL1uE8KQyUIKQVQq3sBqQsPTgiW
|
||||
XT6XznFdWawtW1y2jzt0DbdCt2osSwV7rPYbn6yxEpzQWuPL75JiDmJMktgBV38t
|
||||
FKBpQl1ZDF9wARwsxBvNRaL0XPSurTtf/x4olue91D3I2fNeymS6P1DNXvLGXPD5
|
||||
46C2TvS1305wPjCpFAEgS58WpiiJppCYDsU0A7DEHbaIgk5mQ/iEdBOBvrn0xton
|
||||
Rni5mXt0Vi0WfE8GriR25YajtAOI2/rxTSZjiSJI51WmdnV/lkJiY1YF0ws+KUXG
|
||||
r2E+Bea0kwcnCMYFzraxwLwiS4mgamdQmp8DNALYZe8m/k+dNKI6tDEdWFMTiwda
|
||||
PJMDc3+lUOcZCN6+umLUOVvNWQZ2QtZ4RSTzbJyDww4ysgTpFHOJAcUEAAEIAC8W
|
||||
IQSBoxLFnWedkw+p6LBtco8potur+AUCZPmwIREcdGVzdEBleGFtcGxlLmNvbQAK
|
||||
CRBtco8potur+CMVDACm+OMPck8lmOnNGphP7kKtMcyMyIvsI2Ik/fB3A2z+iXnX
|
||||
TQljubYTqN4Hzv5MECoUhm3GiWJOYjGoXnpec1zAk4VAewin1814Dldq/9CQ+YlN
|
||||
W7HKjCiCBSdk2tPEBUK6gfV+OU9N2mNp/+biuwNTVHpPokIiwBE3MKjtUc0W0xLU
|
||||
N+S/mb1l0MsA91PPRlZqovEFs6214zo8e31pXcWbLbfhbVY32pqDk4eG1JAFjjGM
|
||||
oBASk7z1TH8Ealfj3xr8/HCbfyoaMcSmQzYdEr4E7XOp955IjRU91RrHxYjcxQ9f
|
||||
0VUwak08k6hNBD502jstv5ePJBWoFXYXKuBlaH224Xvhe+9GnaUXsU8rI+OMHJes
|
||||
g4/98+bparaQ8pjhLeh5BoIhojg0y4qNaFmbJraZxG3uhyH0nDVfepOsUj4QOadS
|
||||
VbuULwAy97q+UFwUWuVn/XVzrb3B35TpIJWpvJLF6w5f3pyy58XsVOfVmNyyGuyU
|
||||
jMWUGxIGAsXZusCmsNs=
|
||||
=qVV5
|
||||
-----END PGP SIGNATURE-----
|
16
crates/teepot/tests/data/test2.json
Normal file
16
crates/teepot/tests/data/test2.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"last_digest": "564623a3afcedc19737f7002885bb62529bd8125f4dd6b0d6efd57ab768fb773",
|
||||
"commands": [
|
||||
{
|
||||
"url": "/v1/auth/tee/tees/test",
|
||||
"data": {
|
||||
"lease": "1000",
|
||||
"name": "test",
|
||||
"sgx_allowed_tcb_levels": "Ok,SwHardeningNeeded",
|
||||
"sgx_mrsigner": "c5591a72b8b86e0d8814d6e8750e3efe66aea2d102b8ba2405365559b858697d",
|
||||
"token_policies": "test",
|
||||
"types": "sgx"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
14
crates/teepot/tests/data/test2.json.asc
Normal file
14
crates/teepot/tests/data/test2.json.asc
Normal file
|
@ -0,0 +1,14 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQHFBAABCAAvFiEEgaMSxZ1nnZMPqeiwbXKPKaLbq/gFAmTSQ7sRHHRlc3RAZXhh
|
||||
bXBsZS5jb20ACgkQbXKPKaLbq/jd0Av/Zruw1+nsVFwVIF98IhK4rWoW4vV3Xims
|
||||
wMpAxoV2wTlk55xHbGkWa0BHnjvJr3XlwyJtEI4fT4rL53AgsSd/kp0/2Ml6ExgN
|
||||
LGgziTf+Ct6oHPSM8YDtBXWtFv8YwieSxTYNA1hlCkwpN1d1k8JLZj4i1+U1AhPL
|
||||
nYtM2PLOxxkdFCx7ecp2GCKqsZa1qR+dc+igAHJ4mKKKR9bhhtTxf1tH3nf1BAWu
|
||||
G3Kw+CZISX4IvuUFVC/AA/+g2ySBoLhmk01lzjYvlJHQyJiOlSpHo1nGBvQHZ9cO
|
||||
32I0dn+GOlnzCnsYX4JBsiGrNXG1PfEqL0MbNdrVf8grRC96zwQ9ITio25DaRe1r
|
||||
rO+i68aAS3f9XGGJ1YHUKCAAmCnwPz+x34gH6r6r8LOI+ZRnFKbUDd7Fbuy47EXb
|
||||
oKe3/oGfnY6avn+/2dR1qeHwnN+CSW64UDChWFXyxCD17go7crGq1MhpNvlmmuxW
|
||||
HRI4+gMa4DpiCrzDgCS4ZVaZyzIwIvWw
|
||||
=X/H8
|
||||
-----END PGP SIGNATURE-----
|
3680
crates/teepot/tests/sgx_quote_verification.rs
Normal file
3680
crates/teepot/tests/sgx_quote_verification.rs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue