mirror of
https://github.com/matter-labs/teepot.git
synced 2025-07-21 23:23:57 +02:00
feat: use real RA-TLS for everything
* add `tee-ratls-preexec` for creating the vault certificate * remove the old attestation API Signed-off-by: Harald Hoyer <harald@matterlabs.dev>
This commit is contained in:
parent
020159b9d7
commit
0b60abc030
21 changed files with 837 additions and 834 deletions
402
Cargo.lock
generated
402
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
11
Cargo.toml
11
Cargo.toml
|
@ -27,6 +27,7 @@ hex.workspace = true
|
||||||
intel-tee-quote-verification-rs.workspace = true
|
intel-tee-quote-verification-rs.workspace = true
|
||||||
num-integer.workspace = true
|
num-integer.workspace = true
|
||||||
num-traits.workspace = true
|
num-traits.workspace = true
|
||||||
|
p256.workspace = true
|
||||||
pgp.workspace = true
|
pgp.workspace = true
|
||||||
pkcs8.workspace = true
|
pkcs8.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
|
@ -38,8 +39,10 @@ serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde_with.workspace = true
|
serde_with.workspace = true
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
|
signature.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
webpki-roots.workspace = true
|
||||||
x509-cert.workspace = true
|
x509-cert.workspace = true
|
||||||
zeroize.workspace = true
|
zeroize.workspace = true
|
||||||
|
|
||||||
|
@ -86,10 +89,11 @@ mio = "0.8.10"
|
||||||
num-integer = "0.1.46"
|
num-integer = "0.1.46"
|
||||||
num-traits = "0.2.18"
|
num-traits = "0.2.18"
|
||||||
pgp = "0.11"
|
pgp = "0.11"
|
||||||
|
p256 = "0.13.2"
|
||||||
pkcs8 = { version = "0.10" }
|
pkcs8 = { version = "0.10" }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
ring = { version = "0.17.8", features = ["std"], default-features = false }
|
ring = { version = "0.17.8", features = ["std"], default-features = false }
|
||||||
rsa = { version = "0.9.6", features = ["sha2"] }
|
rsa = { version = "0.9.6", features = ["sha2", "pem"] }
|
||||||
rustls = { version = "0.22" }
|
rustls = { version = "0.22" }
|
||||||
rustls-pemfile = "2"
|
rustls-pemfile = "2"
|
||||||
sec1 = { version = "0.7.3", features = ["der"], default-features = false }
|
sec1 = { version = "0.7.3", features = ["der"], default-features = false }
|
||||||
|
@ -97,6 +101,7 @@ serde = { version = "1", features = ["derive", "rc"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
serde_with = { version = "3.6", features = ["base64", "hex"] }
|
serde_with = { version = "3.6", features = ["base64", "hex"] }
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
|
signature = "2.2.0"
|
||||||
teepot = { path = "." }
|
teepot = { path = "." }
|
||||||
testaso = "0.1.0"
|
testaso = "0.1.0"
|
||||||
thiserror = "1.0.57"
|
thiserror = "1.0.57"
|
||||||
|
@ -105,6 +110,6 @@ tracing = "0.1"
|
||||||
tracing-actix-web = "0.7"
|
tracing-actix-web = "0.7"
|
||||||
tracing-log = "0.2"
|
tracing-log = "0.2"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
x509 = { version = "0.2", package = "x509-cert", default-features = false }
|
x509-cert = { version = "0.2", features = ["builder", "signature"] }
|
||||||
x509-cert = "0.2.5"
|
|
||||||
zeroize = { version = "1.7.0", features = ["serde"] }
|
zeroize = { version = "1.7.0", features = ["serde"] }
|
||||||
|
webpki-roots = "0.26.1"
|
||||||
|
|
21
bin/tee-ratls-preexec/Cargo.toml
Normal file
21
bin/tee-ratls-preexec/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "tee-ratls-preexec"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
clap.workspace = true
|
||||||
|
rsa.workspace = true
|
||||||
|
rustls-pemfile.workspace = true
|
||||||
|
rustls.workspace = true
|
||||||
|
teepot.workspace = true
|
||||||
|
tracing-log.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
x509-cert.workspace = true
|
104
bin/tee-ratls-preexec/src/main.rs
Normal file
104
bin/tee-ratls-preexec/src/main.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (c) 2024 Matter Labs
|
||||||
|
|
||||||
|
//! Pre-exec for binary running in a TEE needing attestation of a secret signing key
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![deny(clippy::all)]
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use clap::Parser;
|
||||||
|
use rsa::pkcs1v15::SigningKey;
|
||||||
|
use rsa::pkcs8::DecodePrivateKey;
|
||||||
|
use rsa::sha2::Sha256;
|
||||||
|
use rsa::RsaPrivateKey;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
use teepot::server::pki::make_signed_cert;
|
||||||
|
use tracing::error;
|
||||||
|
use tracing_log::LogTracer;
|
||||||
|
use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry};
|
||||||
|
use x509_cert::der::asn1::Ia5String;
|
||||||
|
use x509_cert::der::DecodePem;
|
||||||
|
use x509_cert::ext::pkix::name::GeneralName;
|
||||||
|
use x509_cert::Certificate;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// ca cert file
|
||||||
|
#[arg(long, env = "CA_CERT_FILE", default_value = "/opt/vault/cacert.pem")]
|
||||||
|
ca_cert_file: PathBuf,
|
||||||
|
/// ca key file
|
||||||
|
#[arg(long, env = "CA_KEY_FILE", default_value = "/opt/vault/cakey.pem")]
|
||||||
|
ca_key_file: PathBuf,
|
||||||
|
/// out cert file
|
||||||
|
#[arg(long, env = "TLS_CERT_FILE", default_value = "/opt/vault/tls/tls.crt")]
|
||||||
|
tls_cert_file: PathBuf,
|
||||||
|
/// out key file
|
||||||
|
#[arg(long, env = "TLS_KEY_FILE", default_value = "/opt/vault/tls/tls.key")]
|
||||||
|
tls_key_file: PathBuf,
|
||||||
|
/// DNS names, comma separated
|
||||||
|
#[arg(long, env = "DNS_NAMES", required = true)]
|
||||||
|
dns_names: String,
|
||||||
|
/// program to exec [args...] (required)
|
||||||
|
#[arg(required = true, allow_hyphen_values = true, last = true)]
|
||||||
|
cmd_args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main_with_error() -> Result<()> {
|
||||||
|
LogTracer::init().context("Failed to set logger")?;
|
||||||
|
|
||||||
|
let subscriber = Registry::default()
|
||||||
|
.with(EnvFilter::from_default_env())
|
||||||
|
.with(fmt::layer().with_writer(std::io::stderr));
|
||||||
|
tracing::subscriber::set_global_default(subscriber).context("Failed to set logger")?;
|
||||||
|
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
// read `issuer_cert_bytes` from file
|
||||||
|
let ca_cert = std::fs::read(args.ca_cert_file).context("Failed to read ca_cert")?;
|
||||||
|
|
||||||
|
let issuer_cert = Certificate::from_pem(&ca_cert)?;
|
||||||
|
let issuer_key =
|
||||||
|
RsaPrivateKey::read_pkcs8_pem_file(args.ca_key_file).context("Failed to read ca_key")?;
|
||||||
|
let issuer_key_pair = SigningKey::<Sha256>::new(issuer_key);
|
||||||
|
|
||||||
|
// TODO: read values from config file or env or args
|
||||||
|
let dn = "O=system:nodes,CN=system:node";
|
||||||
|
let mut an = vec![std::net::IpAddr::from(std::net::Ipv4Addr::LOCALHOST).into()];
|
||||||
|
an.extend(
|
||||||
|
args.dns_names
|
||||||
|
.split(',')
|
||||||
|
.map(|s| GeneralName::DnsName(Ia5String::try_from(s.to_string()).unwrap())),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (_report_data, cert, priv_key) =
|
||||||
|
make_signed_cert(dn, Some(an), &issuer_cert, &issuer_key_pair)?;
|
||||||
|
|
||||||
|
// open args.tls_cert_file and write cert and ca_cert
|
||||||
|
let mut file = File::create(&args.tls_cert_file).context("Failed to create tls_cert")?;
|
||||||
|
file.write_all(cert.as_bytes())
|
||||||
|
.context("Failed to write tls_cert")?;
|
||||||
|
file.write_all(&ca_cert)
|
||||||
|
.context("Failed to write tls_cert")?;
|
||||||
|
|
||||||
|
std::fs::write(args.tls_key_file, priv_key).context("Failed to write tls_cert")?;
|
||||||
|
|
||||||
|
let err = Command::new(&args.cmd_args[0])
|
||||||
|
.args(&args.cmd_args[1..])
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
Err(err).with_context(|| format!("exec of `{cmd}` failed", cmd = args.cmd_args.join(" ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let ret = main_with_error();
|
||||||
|
if let Err(e) = &ret {
|
||||||
|
error!("Error: {}", e);
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (c) 2024 Matter Labs
|
||||||
|
|
||||||
//! Server to handle requests to the Vault TEE
|
//! Server to handle requests to the Vault TEE
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let args = Arguments::parse();
|
let args = Arguments::parse();
|
||||||
|
|
||||||
let (report_data, _cert_chain, _priv_key) = make_self_signed_cert()?;
|
let (report_data, _cert_chain, _priv_key) = make_self_signed_cert("CN=localhost", None)?;
|
||||||
if let Err(e) = get_quote_and_collateral(Some(args.my_sgx_allowed_tcb_levels), &report_data) {
|
if let Err(e) = get_quote_and_collateral(Some(args.my_sgx_allowed_tcb_levels), &report_data) {
|
||||||
error!("failed to get quote and collateral: {e:?}");
|
error!("failed to get quote and collateral: {e:?}");
|
||||||
// don't return for now, we can still serve requests but we won't be able to attest
|
// don't return for now, we can still serve requests but we won't be able to attest
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// Copyright (c) 2023 Matter Labs
|
|
||||||
|
|
||||||
//! attestation
|
|
||||||
|
|
||||||
use crate::ServerState;
|
|
||||||
use actix_web::http::StatusCode;
|
|
||||||
use actix_web::web::{Data, Json};
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use teepot::json::http::AttestationResponse;
|
|
||||||
use teepot::server::attestation::get_quote_and_collateral;
|
|
||||||
use teepot::server::{HttpResponseError, Status};
|
|
||||||
use tracing::instrument;
|
|
||||||
|
|
||||||
/// Get attestation
|
|
||||||
#[instrument(level = "info", name = "/v1/sys/attestation", skip_all)]
|
|
||||||
pub async fn get_attestation(
|
|
||||||
worker: Data<Arc<ServerState>>,
|
|
||||||
) -> Result<Json<AttestationResponse>, HttpResponseError> {
|
|
||||||
get_quote_and_collateral(None, &worker.report_data)
|
|
||||||
.context("Error getting attestation")
|
|
||||||
.map(Json)
|
|
||||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
|
|
@ -1,12 +1,10 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2023 Matter Labs
|
// Copyright (c) 2023-2024 Matter Labs
|
||||||
|
|
||||||
//! Server to handle requests to the Vault TEE
|
//! Server to handle requests to the Vault TEE
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
|
|
||||||
mod attestation;
|
|
||||||
mod command;
|
mod command;
|
||||||
mod digest;
|
mod digest;
|
||||||
mod sign;
|
mod sign;
|
||||||
|
@ -14,7 +12,6 @@ mod sign;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use attestation::get_attestation;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use command::post_command;
|
use command::post_command;
|
||||||
use digest::get_digest;
|
use digest::get_digest;
|
||||||
|
@ -22,7 +19,7 @@ use rustls::ServerConfig;
|
||||||
use sign::post_sign;
|
use sign::post_sign;
|
||||||
use std::net::Ipv6Addr;
|
use std::net::Ipv6Addr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use teepot::json::http::{SignRequest, VaultCommandRequest, ATTESTATION_URL, DIGEST_URL};
|
use teepot::json::http::{SignRequest, VaultCommandRequest, DIGEST_URL};
|
||||||
use teepot::server::attestation::{get_quote_and_collateral, VaultAttestationArgs};
|
use teepot::server::attestation::{get_quote_and_collateral, VaultAttestationArgs};
|
||||||
use teepot::server::new_json_cfg;
|
use teepot::server::new_json_cfg;
|
||||||
use teepot::server::pki::make_self_signed_cert;
|
use teepot::server::pki::make_self_signed_cert;
|
||||||
|
@ -65,7 +62,7 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let args = Arguments::parse();
|
let args = Arguments::parse();
|
||||||
|
|
||||||
let (report_data, cert_chain, priv_key) = make_self_signed_cert()?;
|
let (report_data, cert_chain, priv_key) = make_self_signed_cert("CN=localhost", None)?;
|
||||||
|
|
||||||
if let Err(e) = get_quote_and_collateral(Some(args.server_sgx_allowed_tcb_levels), &report_data)
|
if let Err(e) = get_quote_and_collateral(Some(args.server_sgx_allowed_tcb_levels), &report_data)
|
||||||
{
|
{
|
||||||
|
@ -94,7 +91,6 @@ async fn main() -> Result<()> {
|
||||||
.wrap(TracingLogger::default())
|
.wrap(TracingLogger::default())
|
||||||
.app_data(new_json_cfg())
|
.app_data(new_json_cfg())
|
||||||
.app_data(Data::new(server_state.clone()))
|
.app_data(Data::new(server_state.clone()))
|
||||||
.service(web::resource(ATTESTATION_URL).route(web::get().to(get_attestation)))
|
|
||||||
.service(web::resource(VaultCommandRequest::URL).route(web::post().to(post_command)))
|
.service(web::resource(VaultCommandRequest::URL).route(web::post().to(post_command)))
|
||||||
.service(web::resource(SignRequest::URL).route(web::post().to(post_sign)))
|
.service(web::resource(SignRequest::URL).route(web::post().to(post_sign)))
|
||||||
.service(web::resource(DIGEST_URL).route(web::get().to(get_digest)))
|
.service(web::resource(DIGEST_URL).route(web::get().to(get_digest)))
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// Copyright (c) 2023 Matter Labs
|
|
||||||
|
|
||||||
use crate::Worker;
|
|
||||||
use actix_web::http::StatusCode;
|
|
||||||
use actix_web::web::{Data, Json};
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use teepot::json::http::AttestationResponse;
|
|
||||||
use teepot::server::attestation::get_quote_and_collateral;
|
|
||||||
use teepot::server::{HttpResponseError, Status};
|
|
||||||
use tracing::instrument;
|
|
||||||
|
|
||||||
#[instrument(level = "info", name = "/v1/sys/attestation", skip_all)]
|
|
||||||
pub async fn get_attestation(
|
|
||||||
worker: Data<Worker>,
|
|
||||||
) -> Result<Json<AttestationResponse>, HttpResponseError> {
|
|
||||||
let report_data: [u8; 64] = worker
|
|
||||||
.config
|
|
||||||
.report_data
|
|
||||||
.clone()
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| "Error getting attestation")?;
|
|
||||||
get_quote_and_collateral(None, &report_data)
|
|
||||||
.context("Error getting attestation")
|
|
||||||
.map(Json)
|
|
||||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
|
|
@ -1,12 +1,13 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2023 Matter Labs
|
// Copyright (c) 2023-2024 Matter Labs
|
||||||
|
|
||||||
use crate::{create_https_client, get_vault_status, UnsealServerState, Worker};
|
use crate::{get_vault_status, UnsealServerState, Worker};
|
||||||
use actix_web::error::ErrorBadRequest;
|
use actix_web::error::ErrorBadRequest;
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpResponse};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use awc::http::StatusCode;
|
use awc::http::StatusCode;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use teepot::client::TeeConnection;
|
||||||
use teepot::json::http::{Init, InitResponse, VaultInitRequest};
|
use teepot::json::http::{Init, InitResponse, VaultInitRequest};
|
||||||
use teepot::json::secrets::AdminConfig;
|
use teepot::json::secrets::AdminConfig;
|
||||||
use teepot::server::{HttpResponseError, Status};
|
use teepot::server::{HttpResponseError, Status};
|
||||||
|
@ -25,7 +26,8 @@ pub async fn post_init(
|
||||||
admin_threshold,
|
admin_threshold,
|
||||||
admin_tee_mrenclave,
|
admin_tee_mrenclave,
|
||||||
} = init.into_inner();
|
} = init.into_inner();
|
||||||
let client = create_https_client(worker.client_tls_config.clone());
|
let conn = TeeConnection::new(&worker.vault_attestation);
|
||||||
|
let client = conn.client();
|
||||||
let vault_url = &worker.config.vault_url;
|
let vault_url = &worker.config.vault_url;
|
||||||
|
|
||||||
let vault_init = VaultInitRequest {
|
let vault_init = VaultInitRequest {
|
||||||
|
@ -62,7 +64,7 @@ pub async fn post_init(
|
||||||
return Err(anyhow!("Vault already initialized")).status(StatusCode::BAD_REQUEST);
|
return Err(anyhow!("Vault already initialized")).status(StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
UnsealServerState::Undefined => {
|
UnsealServerState::Undefined => {
|
||||||
let state = get_vault_status(vault_url, client.clone()).await;
|
let state = get_vault_status(vault_url, client).await;
|
||||||
*worker.state.write().unwrap() = state;
|
*worker.state.write().unwrap() = state;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +1,45 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2023 Matter Labs
|
// Copyright (c) 2023-2024 Matter Labs
|
||||||
|
|
||||||
//! Server to initialize and unseal the Vault TEE.
|
//! Server to initialize and unseal the Vault TEE.
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
|
|
||||||
mod attestation;
|
|
||||||
mod init;
|
mod init;
|
||||||
mod unseal;
|
mod unseal;
|
||||||
|
|
||||||
use actix_web::http::header;
|
|
||||||
use actix_web::rt::time::sleep;
|
use actix_web::rt::time::sleep;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use attestation::get_attestation;
|
use awc::Client;
|
||||||
use awc::{Client, Connector};
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use init::post_init;
|
use init::post_init;
|
||||||
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
|
use rustls::ServerConfig;
|
||||||
use rustls::client::WebPkiServerVerifier;
|
|
||||||
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
|
||||||
use rustls::{ClientConfig, DigitallySignedStruct, Error, ServerConfig, SignatureScheme};
|
|
||||||
use rustls_pemfile::{certs, read_one};
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::net::Ipv6Addr;
|
use std::net::Ipv6Addr;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{fs::File, io::BufReader};
|
use teepot::client::{AttestationArgs, TeeConnection};
|
||||||
use teepot::json::http::{Init, Unseal, ATTESTATION_URL};
|
use teepot::json::http::{Init, Unseal};
|
||||||
use teepot::json::secrets::AdminConfig;
|
use teepot::json::secrets::AdminConfig;
|
||||||
use teepot::server::attestation::get_quote_and_collateral;
|
use teepot::server::attestation::{get_quote_and_collateral, VaultAttestationArgs};
|
||||||
use teepot::server::new_json_cfg;
|
use teepot::server::new_json_cfg;
|
||||||
|
use teepot::server::pki::make_self_signed_cert;
|
||||||
use teepot::sgx::{parse_tcb_levels, EnumSet, TcbLevel};
|
use teepot::sgx::{parse_tcb_levels, EnumSet, TcbLevel};
|
||||||
use tracing::{error, info, trace};
|
use tracing::{error, info};
|
||||||
use tracing_log::LogTracer;
|
use tracing_log::LogTracer;
|
||||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry};
|
use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry};
|
||||||
use unseal::post_unseal;
|
use unseal::post_unseal;
|
||||||
use x509_cert::der::Decode as _;
|
|
||||||
use x509_cert::der::Encode as _;
|
|
||||||
use x509_cert::Certificate;
|
|
||||||
|
|
||||||
const VAULT_AUTH_TEE_SHA256: &str = include_str!("../../../assets/vault-auth-tee.sha256");
|
|
||||||
const VAULT_TOKEN_HEADER: &str = "X-Vault-Token";
|
const VAULT_TOKEN_HEADER: &str = "X-Vault-Token";
|
||||||
|
|
||||||
/// Worker thread state and data
|
/// Worker thread state and data
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Worker {
|
pub struct Worker {
|
||||||
/// TLS config for the HTTPS client
|
/// TLS config for the HTTPS client
|
||||||
pub client_tls_config: Arc<ClientConfig>,
|
pub vault_attestation: Arc<AttestationArgs>,
|
||||||
/// Server config
|
/// Server config
|
||||||
pub config: Arc<UnsealServerConfig>,
|
pub config: Arc<UnsealServerConfig>,
|
||||||
/// Server state
|
/// Server state
|
||||||
|
@ -62,9 +52,13 @@ pub struct UnsealServerConfig {
|
||||||
/// Vault URL
|
/// Vault URL
|
||||||
pub vault_url: String,
|
pub vault_url: String,
|
||||||
/// The expected report_data for the Vault TEE
|
/// The expected report_data for the Vault TEE
|
||||||
pub report_data: Vec<u8>,
|
pub report_data: Box<[u8]>,
|
||||||
/// allowed TCB levels
|
/// allowed TCB levels
|
||||||
pub allowed_tcb_levels: Option<EnumSet<TcbLevel>>,
|
pub allowed_tcb_levels: Option<EnumSet<TcbLevel>>,
|
||||||
|
/// SHA256 of the vault_auth_tee plugin binary
|
||||||
|
pub vault_auth_tee_sha: String,
|
||||||
|
/// version string of the vault_auth_tee plugin
|
||||||
|
pub vault_auth_tee_version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Server state
|
/// Server state
|
||||||
|
@ -90,21 +84,6 @@ pub enum UnsealServerState {
|
||||||
VaultUnsealed,
|
VaultUnsealed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnsealServerConfig {
|
|
||||||
/// Create a new ServerState
|
|
||||||
pub fn new(
|
|
||||||
vault_url: String,
|
|
||||||
report_data: [u8; 64],
|
|
||||||
allowed_tcb_levels: Option<EnumSet<TcbLevel>>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
report_data: report_data.to_vec(),
|
|
||||||
vault_url,
|
|
||||||
allowed_tcb_levels,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
@ -114,9 +93,12 @@ struct Args {
|
||||||
/// port to listen on
|
/// port to listen on
|
||||||
#[arg(long, env = "PORT", default_value = "8443")]
|
#[arg(long, env = "PORT", default_value = "8443")]
|
||||||
port: u16,
|
port: u16,
|
||||||
/// vault url
|
#[arg(long, env = "VAULT_AUTH_TEE_SHA256")]
|
||||||
#[arg(long, env = "VAULT_ADDR", default_value = "https://vault:8210")]
|
vault_auth_tee_sha: String,
|
||||||
vault_url: String,
|
#[arg(long, env = "VAULT_AUTH_TEE_VERSION")]
|
||||||
|
vault_auth_tee_version: String,
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub attestation: VaultAttestationArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
|
@ -134,55 +116,50 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let tls_ok = std::path::Path::new("/opt/vault/tls/tls.ok");
|
|
||||||
loop {
|
|
||||||
info!("Waiting for TLS key/cert files to be generated");
|
|
||||||
|
|
||||||
// Wait for the file `data/tls.key` to exist
|
|
||||||
if tls_ok.exists() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sleep(Duration::from_secs(1)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Starting up");
|
info!("Starting up");
|
||||||
|
|
||||||
let (config, client_tls_config, report_data) = load_rustls_config().or_else(|e| {
|
if let Err(e) = get_quote_and_collateral(Some(args.allowed_tcb_levels), &[0u8; 64]) {
|
||||||
error!("failed to load rustls config: {e:?}");
|
|
||||||
Err(e).context("Failed to load rustls config")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Err(e) = get_quote_and_collateral(Some(args.allowed_tcb_levels), &report_data) {
|
|
||||||
error!("failed to get quote and collateral: {e:?}");
|
error!("failed to get quote and collateral: {e:?}");
|
||||||
// don't return for now, we can still serve requests but we won't be able to attest
|
// don't return for now, we can still serve requests but we won't be able to attest
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = create_https_client(client_tls_config.clone());
|
let (report_data, cert_chain, priv_key) = make_self_signed_cert("CN=localhost", None)?;
|
||||||
|
|
||||||
let server_state = get_vault_status(&args.vault_url, client).await;
|
// init server config builder with safe defaults
|
||||||
|
let config = ServerConfig::builder()
|
||||||
|
.with_no_client_auth()
|
||||||
|
.with_single_cert([cert_chain].into(), priv_key)
|
||||||
|
.context("Failed to load TLS key/cert files")?;
|
||||||
|
|
||||||
|
let attestation_args: AttestationArgs = args.attestation.clone().into();
|
||||||
|
|
||||||
|
let conn = TeeConnection::new(&attestation_args);
|
||||||
|
|
||||||
|
let server_state = get_vault_status(&args.attestation.vault_addr, conn.client()).await;
|
||||||
|
|
||||||
info!("Starting HTTPS server at port {}", args.port);
|
info!("Starting HTTPS server at port {}", args.port);
|
||||||
let server_config = Arc::new(UnsealServerConfig::new(
|
let server_config = Arc::new(UnsealServerConfig {
|
||||||
args.vault_url,
|
vault_url: args.attestation.vault_addr,
|
||||||
report_data,
|
report_data: Box::from(report_data),
|
||||||
Some(args.allowed_tcb_levels),
|
allowed_tcb_levels: Some(args.allowed_tcb_levels),
|
||||||
));
|
vault_auth_tee_sha: args.vault_auth_tee_sha,
|
||||||
|
vault_auth_tee_version: args.vault_auth_tee_version,
|
||||||
|
});
|
||||||
|
|
||||||
let server_state = Arc::new(RwLock::new(server_state));
|
let server_state = Arc::new(RwLock::new(server_state));
|
||||||
|
|
||||||
let server = match HttpServer::new(move || {
|
|
||||||
let worker = Worker {
|
let worker = Worker {
|
||||||
client_tls_config: client_tls_config.clone(),
|
vault_attestation: Arc::new(attestation_args),
|
||||||
config: server_config.clone(),
|
config: server_config,
|
||||||
state: server_state.clone(),
|
state: server_state,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let server = match HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
// enable logger
|
// enable logger
|
||||||
//.wrap(TracingLogger::default())
|
//.wrap(TracingLogger::default())
|
||||||
.app_data(new_json_cfg())
|
.app_data(new_json_cfg())
|
||||||
.app_data(Data::new(worker))
|
.app_data(Data::new(worker.clone()))
|
||||||
.service(web::resource(ATTESTATION_URL).route(web::get().to(get_attestation)))
|
|
||||||
.service(web::resource(Init::URL).route(web::post().to(post_init)))
|
.service(web::resource(Init::URL).route(web::post().to(post_init)))
|
||||||
.service(web::resource(Unseal::URL).route(web::post().to(post_unseal)))
|
.service(web::resource(Unseal::URL).route(web::post().to(post_unseal)))
|
||||||
})
|
})
|
||||||
|
@ -203,7 +180,7 @@ async fn main() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_vault_status(vault_url: &str, client: Client) -> UnsealServerState {
|
async fn get_vault_status(vault_url: &str, client: &Client) -> UnsealServerState {
|
||||||
loop {
|
loop {
|
||||||
let r = client
|
let r = client
|
||||||
.get(format!("{}/v1/sys/health", vault_url))
|
.get(format!("{}/v1/sys/health", vault_url))
|
||||||
|
@ -234,130 +211,3 @@ async fn get_vault_status(vault_url: &str, client: Client) -> UnsealServerState
|
||||||
sleep(Duration::from_secs(1)).await;
|
sleep(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the hash of the public server key to `REPORT_DATA` to check
|
|
||||||
// the attestations against it and it does not change on reconnect.
|
|
||||||
fn make_verifier(server_cert: Box<[u8]>) -> impl ServerCertVerifier {
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct V {
|
|
||||||
server_cert: Box<[u8]>,
|
|
||||||
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,
|
|
||||||
) -> std::result::Result<ServerCertVerified, Error> {
|
|
||||||
let data = &self.server_cert;
|
|
||||||
|
|
||||||
if data.as_ref() == end_entity.as_ref() {
|
|
||||||
info!("Server certificate matches expected certificate");
|
|
||||||
Ok(ServerCertVerified::assertion())
|
|
||||||
} else {
|
|
||||||
error!("Server certificate does not match expected certificate");
|
|
||||||
Err(rustls::Error::General(
|
|
||||||
"Server certificate does not match expected certificate".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 root_store = Arc::new(rustls::RootCertStore::empty());
|
|
||||||
let server_verifier = WebPkiServerVerifier::builder(root_store).build().unwrap();
|
|
||||||
V {
|
|
||||||
server_cert,
|
|
||||||
server_verifier,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load TLS key/cert files
|
|
||||||
pub fn load_rustls_config() -> Result<(ServerConfig, Arc<ClientConfig>, [u8; 64])> {
|
|
||||||
// init server config builder with safe defaults
|
|
||||||
let config = ServerConfig::builder().with_no_client_auth();
|
|
||||||
|
|
||||||
// load TLS key/cert files
|
|
||||||
let cert_file = &mut BufReader::new(
|
|
||||||
File::open("/opt/vault/tls/tls.crt").context("Failed to open TLS cert file")?,
|
|
||||||
);
|
|
||||||
let key_file = &mut BufReader::new(
|
|
||||||
File::open("/opt/vault/tls/tls.key").context("Failed to open TLS key file")?,
|
|
||||||
);
|
|
||||||
|
|
||||||
// convert files to key/cert objects
|
|
||||||
let cert_chain = certs(cert_file)
|
|
||||||
.collect::<Result<Vec<_>, _>>()
|
|
||||||
.context("Failed to load TLS cert file")?;
|
|
||||||
|
|
||||||
let priv_key: rustls::pki_types::PrivateKeyDer =
|
|
||||||
match read_one(key_file).context("Failed to read TLS key file")? {
|
|
||||||
Some(rustls_pemfile::Item::Sec1Key(key)) => key.into(),
|
|
||||||
Some(rustls_pemfile::Item::Pkcs1Key(key)) => key.into(),
|
|
||||||
Some(rustls_pemfile::Item::Pkcs8Key(key)) => key.into(),
|
|
||||||
_ => bail!("no keys found in TLS key file"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tls_config = Arc::new(
|
|
||||||
ClientConfig::builder()
|
|
||||||
.dangerous()
|
|
||||||
.with_custom_certificate_verifier(Arc::new(make_verifier(
|
|
||||||
cert_chain[0].as_ref().into(),
|
|
||||||
)))
|
|
||||||
.with_no_client_auth(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let cert = Certificate::from_der(cert_chain[0].as_ref()).unwrap();
|
|
||||||
let pub_key = cert
|
|
||||||
.tbs_certificate
|
|
||||||
.subject_public_key_info
|
|
||||||
.to_der()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let hash = Sha256::digest(pub_key);
|
|
||||||
let mut report_data = [0u8; 64];
|
|
||||||
report_data[..32].copy_from_slice(&hash[..32]);
|
|
||||||
|
|
||||||
let report_data_hex = hex::encode(report_data);
|
|
||||||
trace!(report_data_hex);
|
|
||||||
|
|
||||||
let config = config
|
|
||||||
.with_single_cert(cert_chain, priv_key)
|
|
||||||
.context("Failed to load TLS key/cert files")?;
|
|
||||||
|
|
||||||
Ok((config, tls_config, report_data))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an HTTPS client with the default headers and config
|
|
||||||
pub fn create_https_client(client_tls_config: Arc<ClientConfig>) -> 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(client_tls_config))
|
|
||||||
.timeout(Duration::from_secs(12000))
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2023 Matter Labs
|
// Copyright (c) 2023-2024 Matter Labs
|
||||||
|
|
||||||
use crate::{
|
use crate::{get_vault_status, UnsealServerConfig, UnsealServerState, Worker, VAULT_TOKEN_HEADER};
|
||||||
create_https_client, get_vault_status, UnsealServerConfig, UnsealServerState, Worker,
|
|
||||||
VAULT_AUTH_TEE_SHA256, VAULT_TOKEN_HEADER,
|
|
||||||
};
|
|
||||||
use actix_web::http::StatusCode;
|
use actix_web::http::StatusCode;
|
||||||
use actix_web::rt::time::sleep;
|
use actix_web::rt::time::sleep;
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpResponse};
|
||||||
|
@ -16,6 +13,7 @@ use std::future::Future;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use teepot::client::vault::VaultConnection;
|
use teepot::client::vault::VaultConnection;
|
||||||
|
use teepot::client::TeeConnection;
|
||||||
use teepot::json::http::Unseal;
|
use teepot::json::http::Unseal;
|
||||||
use teepot::json::secrets::{AdminConfig, AdminState};
|
use teepot::json::secrets::{AdminConfig, AdminState};
|
||||||
use teepot::server::{HttpResponseError, Status};
|
use teepot::server::{HttpResponseError, Status};
|
||||||
|
@ -26,7 +24,8 @@ pub async fn post_unseal(
|
||||||
worker: web::Data<Worker>,
|
worker: web::Data<Worker>,
|
||||||
item: web::Json<Unseal>,
|
item: web::Json<Unseal>,
|
||||||
) -> Result<HttpResponse, HttpResponseError> {
|
) -> Result<HttpResponse, HttpResponseError> {
|
||||||
let client = create_https_client(worker.client_tls_config.clone());
|
let conn = TeeConnection::new(&worker.vault_attestation);
|
||||||
|
let client = conn.client();
|
||||||
let app = &worker.config;
|
let app = &worker.config;
|
||||||
let vault_url = &app.vault_url;
|
let vault_url = &app.vault_url;
|
||||||
|
|
||||||
|
@ -46,7 +45,7 @@ pub async fn post_unseal(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
UnsealServerState::Undefined => {
|
UnsealServerState::Undefined => {
|
||||||
let state = get_vault_status(vault_url, client.clone()).await;
|
let state = get_vault_status(vault_url, client).await;
|
||||||
*worker.state.write().unwrap() = state;
|
*worker.state.write().unwrap() = state;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -106,15 +105,13 @@ pub async fn post_unseal(
|
||||||
} => {
|
} => {
|
||||||
debug!(root_token);
|
debug!(root_token);
|
||||||
info!("Vault is unsealed");
|
info!("Vault is unsealed");
|
||||||
let app = &worker.config;
|
|
||||||
let client = create_https_client(worker.client_tls_config.clone());
|
|
||||||
|
|
||||||
vault_configure_unsealed(
|
vault_configure_unsealed(
|
||||||
app,
|
app,
|
||||||
&admin_config,
|
&admin_config,
|
||||||
&root_token,
|
&root_token,
|
||||||
&admin_tee_mrenclave,
|
&admin_tee_mrenclave,
|
||||||
&client,
|
client,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("Failed to configure unsealed vault")
|
.context("Failed to configure unsealed vault")
|
||||||
|
@ -204,9 +201,9 @@ pub async fn vault_configure_unsealed(
|
||||||
)),
|
)),
|
||||||
root_token,
|
root_token,
|
||||||
json!({
|
json!({
|
||||||
"sha256": VAULT_AUTH_TEE_SHA256,
|
"sha256": app.vault_auth_tee_sha,
|
||||||
"command": "vault-auth-tee",
|
"command": "vault-auth-tee",
|
||||||
"version": "0.1.0+dev"
|
"version": app.vault_auth_tee_version
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -374,7 +371,7 @@ async fn plugin_is_already_running(
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||||
.and_then(|v| {
|
.and_then(|v| {
|
||||||
if v == VAULT_AUTH_TEE_SHA256 {
|
if v == app.vault_auth_tee_sha {
|
||||||
Some(v)
|
Some(v)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2023 Matter Labs
|
// Copyright (c) 2023-2024 Matter Labs
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
@ -13,7 +13,7 @@ use std::path::{Path, PathBuf};
|
||||||
use teepot::client::{AttestationArgs, TeeConnection};
|
use teepot::client::{AttestationArgs, TeeConnection};
|
||||||
use teepot::json::http::{
|
use teepot::json::http::{
|
||||||
SignRequest, SignRequestData, SignResponse, VaultCommandRequest, VaultCommands,
|
SignRequest, SignRequestData, SignResponse, VaultCommandRequest, VaultCommands,
|
||||||
VaultCommandsResponse, ATTESTATION_URL, DIGEST_URL,
|
VaultCommandsResponse, DIGEST_URL,
|
||||||
};
|
};
|
||||||
use teepot::server::signatures::verify_sig;
|
use teepot::server::signatures::verify_sig;
|
||||||
use teepot::sgx::sign::Signature;
|
use teepot::sgx::sign::Signature;
|
||||||
|
@ -221,7 +221,7 @@ async fn send_commands(args: SendArgs) -> Result<()> {
|
||||||
signatures,
|
signatures,
|
||||||
};
|
};
|
||||||
|
|
||||||
let conn = TeeConnection::new(&args.attestation, ATTESTATION_URL).await?;
|
let conn = TeeConnection::new(&args.attestation);
|
||||||
|
|
||||||
let mut response = conn
|
let mut response = conn
|
||||||
.client()
|
.client()
|
||||||
|
@ -299,7 +299,7 @@ async fn send_sig_request(args: SignTeeArgs) -> Result<()> {
|
||||||
signatures,
|
signatures,
|
||||||
};
|
};
|
||||||
|
|
||||||
let conn = TeeConnection::new(&args.attestation, ATTESTATION_URL).await?;
|
let conn = TeeConnection::new(&args.attestation);
|
||||||
|
|
||||||
let mut response = conn
|
let mut response = conn
|
||||||
.client()
|
.client()
|
||||||
|
@ -338,7 +338,7 @@ async fn send_sig_request(args: SignTeeArgs) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn digest(args: DigestArgs) -> Result<()> {
|
async fn digest(args: DigestArgs) -> Result<()> {
|
||||||
let conn = TeeConnection::new(&args.attestation, ATTESTATION_URL).await?;
|
let conn = TeeConnection::new(&args.attestation);
|
||||||
|
|
||||||
let mut response = conn
|
let mut response = conn
|
||||||
.client()
|
.client()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2023 Matter Labs
|
// Copyright (c) 2023-2024 Matter Labs
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
@ -8,7 +8,7 @@ use serde_json::Value;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use teepot::client::{AttestationArgs, TeeConnection};
|
use teepot::client::{AttestationArgs, TeeConnection};
|
||||||
use teepot::json::http::{Init, InitResponse, Unseal, ATTESTATION_URL};
|
use teepot::json::http::{Init, InitResponse, Unseal};
|
||||||
use tracing::{error, info, trace, warn};
|
use tracing::{error, info, trace, warn};
|
||||||
use tracing_log::LogTracer;
|
use tracing_log::LogTracer;
|
||||||
use tracing_subscriber::Registry;
|
use tracing_subscriber::Registry;
|
||||||
|
@ -70,7 +70,7 @@ async fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init(args: Arguments) -> Result<()> {
|
async fn init(args: Arguments) -> Result<()> {
|
||||||
let conn = TeeConnection::new(&args.attestation, ATTESTATION_URL).await?;
|
let conn = TeeConnection::new(&args.attestation);
|
||||||
|
|
||||||
info!("Quote verified! Connection secure!");
|
info!("Quote verified! Connection secure!");
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ async fn unseal(args: Arguments) -> Result<()> {
|
||||||
bail!("Error reading key from stdin");
|
bail!("Error reading key from stdin");
|
||||||
}
|
}
|
||||||
|
|
||||||
let conn = TeeConnection::new(&args.attestation, ATTESTATION_URL).await?;
|
let conn = TeeConnection::new(&args.attestation);
|
||||||
|
|
||||||
info!("Quote verified! Connection secure!");
|
info!("Quote verified! Connection secure!");
|
||||||
|
|
||||||
|
|
|
@ -8,26 +8,27 @@
|
||||||
|
|
||||||
pub mod vault;
|
pub mod vault;
|
||||||
|
|
||||||
use crate::json::http::AttestationResponse;
|
use crate::server::pki::{RaTlsCollateralExtension, RaTlsQuoteExtension};
|
||||||
use crate::sgx::Collateral;
|
use crate::sgx::Quote;
|
||||||
pub use crate::sgx::{
|
pub use crate::sgx::{
|
||||||
parse_tcb_levels, sgx_ql_qv_result_t, verify_quote_with_collateral, EnumSet,
|
parse_tcb_levels, sgx_ql_qv_result_t, verify_quote_with_collateral, EnumSet,
|
||||||
QuoteVerificationResult, TcbLevel,
|
QuoteVerificationResult, TcbLevel,
|
||||||
};
|
};
|
||||||
use actix_web::http::header;
|
use actix_web::http::header;
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::Result;
|
||||||
use awc::{Client, Connector};
|
use awc::{Client, Connector};
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
use const_oid::AssociatedOid;
|
||||||
|
use intel_tee_quote_verification_rs::Collateral;
|
||||||
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier};
|
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier};
|
||||||
use rustls::client::WebPkiServerVerifier;
|
use rustls::client::WebPkiServerVerifier;
|
||||||
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
||||||
use rustls::{ClientConfig, DigitallySignedStruct, Error, SignatureScheme};
|
use rustls::{ClientConfig, DigitallySignedStruct, Error, SignatureScheme};
|
||||||
use serde_json::Value;
|
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::Arc;
|
||||||
use std::time;
|
use std::time;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{debug, error, info, trace, warn};
|
||||||
use x509_cert::der::{Decode as _, Encode as _};
|
use x509_cert::der::{Decode as _, Encode as _};
|
||||||
use x509_cert::Certificate;
|
use x509_cert::Certificate;
|
||||||
|
|
||||||
|
@ -61,13 +62,11 @@ impl TeeConnection {
|
||||||
///
|
///
|
||||||
/// This will verify the attestation report and check that the enclave
|
/// This will verify the attestation report and check that the enclave
|
||||||
/// is running the expected code.
|
/// is running the expected code.
|
||||||
pub async fn new(args: &AttestationArgs, attestation_url: &str) -> Result<Self> {
|
pub fn new(args: &AttestationArgs) -> Self {
|
||||||
let pk_hash = Arc::new(OnceLock::new());
|
|
||||||
|
|
||||||
let tls_config = Arc::new(
|
let tls_config = Arc::new(
|
||||||
ClientConfig::builder()
|
ClientConfig::builder()
|
||||||
.dangerous()
|
.dangerous()
|
||||||
.with_custom_certificate_verifier(Arc::new(Self::make_verifier(pk_hash.clone())))
|
.with_custom_certificate_verifier(Arc::new(Self::make_verifier(args.clone())))
|
||||||
.with_no_client_auth(),
|
.with_no_client_auth(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -78,15 +77,10 @@ impl TeeConnection {
|
||||||
.timeout(Duration::from_secs(12000))
|
.timeout(Duration::from_secs(12000))
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
let this = Self {
|
Self {
|
||||||
server: args.server.clone(),
|
server: args.server.clone(),
|
||||||
client: agent,
|
client: agent,
|
||||||
};
|
}
|
||||||
|
|
||||||
this.check_attestation(args, attestation_url, pk_hash)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new connection to a TEE
|
/// Create a new connection to a TEE
|
||||||
|
@ -110,131 +104,12 @@ impl TeeConnection {
|
||||||
&self.server
|
&self.server
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_attestation(
|
|
||||||
&self,
|
|
||||||
args: &AttestationArgs,
|
|
||||||
attestation_url: &str,
|
|
||||||
pk_hash: Arc<OnceLock<[u8; 32]>>,
|
|
||||||
) -> Result<()> {
|
|
||||||
info!("Getting attestation report");
|
|
||||||
|
|
||||||
let mut response = self
|
|
||||||
.client
|
|
||||||
.get(&format!("{}{attestation_url}", args.server))
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|e| anyhow!("Error sending attestation request: {}", e))?;
|
|
||||||
|
|
||||||
let status_code = response.status();
|
|
||||||
if !status_code.is_success() {
|
|
||||||
error!("Failed to get attestation: {}", status_code);
|
|
||||||
if let Ok(r) = response.json::<Value>().await {
|
|
||||||
eprintln!("Failed to get attestation: {}", r);
|
|
||||||
}
|
|
||||||
bail!("failed to get attestation: {}", status_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
let attestation: AttestationResponse =
|
|
||||||
response.json().await.context("failed to get attestation")?;
|
|
||||||
|
|
||||||
let current_time: i64 = time::SystemTime::now()
|
|
||||||
.duration_since(time::UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs() as _;
|
|
||||||
|
|
||||||
info!("Verifying attestation report");
|
|
||||||
|
|
||||||
let quote: &[u8] = &attestation.quote;
|
|
||||||
let collateral: Option<&Collateral> = Some(&attestation.collateral);
|
|
||||||
let pk_hash = pk_hash.get().unwrap();
|
|
||||||
|
|
||||||
Self::check_attestation_args(args, current_time, quote, collateral, pk_hash)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check the attestation report against `AttestationArgs`
|
|
||||||
pub fn check_attestation_args(
|
|
||||||
args: &AttestationArgs,
|
|
||||||
current_time: i64,
|
|
||||||
quote: &[u8],
|
|
||||||
collateral: Option<&Collateral>,
|
|
||||||
pk_hash: &[u8; 32],
|
|
||||||
) -> Result<()> {
|
|
||||||
let QuoteVerificationResult {
|
|
||||||
collateral_expired,
|
|
||||||
result,
|
|
||||||
quote,
|
|
||||||
advisories,
|
|
||||||
..
|
|
||||||
} = verify_quote_with_collateral(quote, collateral, current_time).unwrap();
|
|
||||||
|
|
||||||
if collateral_expired || !matches!(result, sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK) {
|
|
||||||
if collateral_expired {
|
|
||||||
error!("Collateral is out of date!");
|
|
||||||
bail!("Collateral is out of date!");
|
|
||||||
}
|
|
||||||
|
|
||||||
let tcblevel = TcbLevel::from(result);
|
|
||||||
if args
|
|
||||||
.sgx_allowed_tcb_levels
|
|
||||||
.map_or(true, |levels| !levels.contains(tcblevel))
|
|
||||||
{
|
|
||||||
error!("Quote verification result: {}", tcblevel);
|
|
||||||
bail!("Quote verification result: {}", tcblevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("TcbLevel is allowed: {}", tcblevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
for advisory in advisories {
|
|
||||||
warn!("Info: Advisory ID: {advisory}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if "e.report_body.reportdata[..32] != pk_hash {
|
|
||||||
error!("Report data mismatch");
|
|
||||||
bail!("Report data mismatch");
|
|
||||||
} else {
|
|
||||||
info!(
|
|
||||||
"Report data matches `{}`",
|
|
||||||
hex::encode("e.report_body.reportdata[..32])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mrsigner) = &args.sgx_mrsigner {
|
|
||||||
let mrsigner_bytes = hex::decode(mrsigner).context("Failed to decode mrsigner")?;
|
|
||||||
if quote.report_body.mrsigner[..] != mrsigner_bytes {
|
|
||||||
bail!(
|
|
||||||
"mrsigner mismatch: got {}, expected {}",
|
|
||||||
hex::encode(quote.report_body.mrsigner),
|
|
||||||
&mrsigner
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
info!("mrsigner `{mrsigner}` matches");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mrenclave) = &args.sgx_mrenclave {
|
|
||||||
let mrenclave_bytes = hex::decode(mrenclave).context("Failed to decode mrenclave")?;
|
|
||||||
if quote.report_body.mrenclave[..] != mrenclave_bytes {
|
|
||||||
bail!(
|
|
||||||
"mrenclave mismatch: got {}, expected {}",
|
|
||||||
hex::encode(quote.report_body.mrenclave),
|
|
||||||
&mrenclave
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
info!("mrenclave `{mrenclave}` matches");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Save the hash of the public server key to `REPORT_DATA` to check
|
/// Save the hash of the public server key to `REPORT_DATA` to check
|
||||||
/// the attestations against it and it does not change on reconnect.
|
/// the attestations against it and it does not change on reconnect.
|
||||||
pub fn make_verifier(pk_hash: Arc<OnceLock<[u8; 32]>>) -> impl ServerCertVerifier {
|
pub fn make_verifier(args: AttestationArgs) -> impl ServerCertVerifier {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct V {
|
struct V {
|
||||||
pk_hash: Arc<OnceLock<[u8; 32]>>,
|
args: AttestationArgs,
|
||||||
server_verifier: Arc<WebPkiServerVerifier>,
|
server_verifier: Arc<WebPkiServerVerifier>,
|
||||||
}
|
}
|
||||||
impl ServerCertVerifier for V {
|
impl ServerCertVerifier for V {
|
||||||
|
@ -255,20 +130,133 @@ impl TeeConnection {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let hash = Sha256::digest(pub_key);
|
let hash = Sha256::digest(pub_key);
|
||||||
let data = self.pk_hash.get_or_init(|| hash[..32].try_into().unwrap());
|
|
||||||
|
|
||||||
if data == &hash[..32] {
|
let exts = cert
|
||||||
info!(
|
.tbs_certificate
|
||||||
"Checked or set server certificate public key hash `{}`",
|
.extensions
|
||||||
hex::encode(&hash[..32])
|
.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(rustls::client::danger::ServerCertVerified::assertion())
|
})
|
||||||
} else {
|
.ok()
|
||||||
error!("Server certificate does not match expected certificate");
|
});
|
||||||
Err(rustls::Error::General(
|
|
||||||
"Server certificate does not match expected certificate".to_string(),
|
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(
|
fn verify_tls12_signature(
|
||||||
|
@ -295,10 +283,14 @@ impl TeeConnection {
|
||||||
self.server_verifier.supported_verify_schemes()
|
self.server_verifier.supported_verify_schemes()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let root_store = Arc::new(rustls::RootCertStore::empty());
|
let mut root_store = rustls::RootCertStore::empty();
|
||||||
let server_verifier = WebPkiServerVerifier::builder(root_store).build().unwrap();
|
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||||
|
let server_verifier = WebPkiServerVerifier::builder(Arc::new(root_store))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
V {
|
V {
|
||||||
pk_hash,
|
args,
|
||||||
server_verifier,
|
server_verifier,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2023 Matter Labs
|
// Copyright (c) 2023-2024 Matter Labs
|
||||||
|
|
||||||
//! Helper functions for CLI clients to verify Intel SGX enclaves and other TEEs.
|
//! Helper functions for CLI clients to verify Intel SGX enclaves and other TEEs.
|
||||||
|
|
||||||
|
@ -22,11 +22,10 @@ use awc::error::{SendRequestError, StatusCode};
|
||||||
use awc::{Client, ClientResponse, Connector};
|
use awc::{Client, ClientResponse, Connector};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use getrandom::getrandom;
|
|
||||||
use rustls::ClientConfig;
|
use rustls::ClientConfig;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::Arc;
|
||||||
use std::time;
|
use std::time;
|
||||||
use tracing::{debug, error, info, trace};
|
use tracing::{debug, error, info, trace};
|
||||||
|
|
||||||
|
@ -85,15 +84,14 @@ impl VaultConnection {
|
||||||
/// This will verify the attestation report and check that the enclave
|
/// This will verify the attestation report and check that the enclave
|
||||||
/// is running the expected code.
|
/// is running the expected code.
|
||||||
pub async fn new(args: &AttestationArgs, name: String) -> Result<Self> {
|
pub async fn new(args: &AttestationArgs, name: String) -> Result<Self> {
|
||||||
let pk_hash = Arc::new(OnceLock::new());
|
let (key_hash, rustls_certificate, rustls_pk) =
|
||||||
|
make_self_signed_cert("CN=localhost", None)?;
|
||||||
let (key_hash, rustls_certificate, rustls_pk) = make_self_signed_cert()?;
|
|
||||||
|
|
||||||
let tls_config = Arc::new(
|
let tls_config = Arc::new(
|
||||||
ClientConfig::builder()
|
ClientConfig::builder()
|
||||||
.dangerous()
|
.dangerous()
|
||||||
.with_custom_certificate_verifier(Arc::new(TeeConnection::make_verifier(
|
.with_custom_certificate_verifier(Arc::new(TeeConnection::make_verifier(
|
||||||
pk_hash.clone(),
|
args.clone(),
|
||||||
)))
|
)))
|
||||||
.with_client_auth_cert(vec![rustls_certificate], rustls_pk)?,
|
.with_client_auth_cert(vec![rustls_certificate], rustls_pk)?,
|
||||||
);
|
);
|
||||||
|
@ -114,7 +112,7 @@ impl VaultConnection {
|
||||||
client_token: Default::default(),
|
client_token: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.client_token = this.auth(args).await?.auth.client_token;
|
this.client_token = this.auth().await?.auth.client_token;
|
||||||
|
|
||||||
trace!("Got Token: {:#?}", &this.client_token);
|
trace!("Got Token: {:#?}", &this.client_token);
|
||||||
|
|
||||||
|
@ -147,24 +145,18 @@ impl VaultConnection {
|
||||||
self.conn.client()
|
self.conn.client()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn auth(&self, args: &AttestationArgs) -> Result<AuthResponse> {
|
async fn auth(&self) -> Result<AuthResponse> {
|
||||||
info!("Getting attestation report");
|
info!("Getting attestation report");
|
||||||
let attestation_url = AuthRequest::URL;
|
let attestation_url = AuthRequest::URL;
|
||||||
let quote = sgx_gramine_get_quote(&self.key_hash).context("Failed to get own quote")?;
|
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 collateral = tee_qv_get_collateral("e).context("Failed to get own collateral")?;
|
||||||
|
|
||||||
let mut challenge_bytes = [0u8; 32];
|
|
||||||
getrandom(&mut challenge_bytes)?;
|
|
||||||
let challenge = hex::encode(challenge_bytes);
|
|
||||||
info!("Challenging Vault with: {}", challenge);
|
|
||||||
let challenge = Some(challenge_bytes);
|
|
||||||
|
|
||||||
let auth_req = AuthRequest {
|
let auth_req = AuthRequest {
|
||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
tee_type: "sgx".to_string(),
|
tee_type: "sgx".to_string(),
|
||||||
quote,
|
quote,
|
||||||
collateral: serde_json::to_string(&collateral)?,
|
collateral: serde_json::to_string(&collateral)?,
|
||||||
challenge,
|
challenge: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut response = self
|
let mut response = self
|
||||||
|
@ -197,26 +189,6 @@ impl VaultConnection {
|
||||||
|
|
||||||
trace!("Got AuthResponse: {:#?}", &auth_response);
|
trace!("Got AuthResponse: {:#?}", &auth_response);
|
||||||
|
|
||||||
let current_time: i64 = time::SystemTime::now()
|
|
||||||
.duration_since(time::UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs() as _;
|
|
||||||
|
|
||||||
info!("Verifying attestation report");
|
|
||||||
|
|
||||||
let collateral: Option<Collateral> =
|
|
||||||
serde_json::from_str(&auth_response.data.collateral).ok();
|
|
||||||
let collateral = collateral.as_ref();
|
|
||||||
|
|
||||||
TeeConnection::check_attestation_args(
|
|
||||||
args,
|
|
||||||
current_time,
|
|
||||||
&auth_response.data.quote,
|
|
||||||
collateral,
|
|
||||||
&challenge_bytes,
|
|
||||||
)
|
|
||||||
.context("Failed to verify Vault attestation report")?;
|
|
||||||
|
|
||||||
Ok(auth_response)
|
Ok(auth_response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,7 +278,10 @@ impl VaultConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if rel_path is alphanumeric
|
// check if rel_path is alphanumeric
|
||||||
if !rel_path.chars().all(|c| c.is_alphanumeric() || c == '_') {
|
if !rel_path
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_alphanumeric() || c == '_' || c == '/')
|
||||||
|
{
|
||||||
return Err(anyhow!("path is not alphanumeric")).status(StatusCode::BAD_REQUEST);
|
return Err(anyhow!("path is not alphanumeric")).status(StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2023 Matter Labs
|
// Copyright (c) 2023-2024 Matter Labs
|
||||||
|
|
||||||
//! Common types for the teepot http JSON API
|
//! Common types for the teepot http JSON API
|
||||||
|
|
||||||
|
@ -23,9 +23,6 @@ impl Unseal {
|
||||||
pub const URL: &'static str = "/v1/sys/unseal";
|
pub const URL: &'static str = "/v1/sys/unseal";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The attestation URL
|
|
||||||
pub const ATTESTATION_URL: &str = "/v1/sys/attestation";
|
|
||||||
|
|
||||||
/// The attestation response
|
/// The attestation response
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct AttestationResponse {
|
pub struct AttestationResponse {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2023 Matter Labs
|
// Copyright (c) 2023-2024 Matter Labs
|
||||||
|
|
||||||
//! Common attestation API for all TEEs
|
//! Common attestation API for all TEEs
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2023 Matter Labs
|
// Copyright (c) 2023-2024 Matter Labs
|
||||||
|
|
||||||
//! # tee-server
|
//! # tee-server
|
||||||
|
|
||||||
|
@ -78,9 +78,15 @@ impl HttpResponseError {
|
||||||
S: Stream<Item = Result<Bytes, PayloadError>>,
|
S: Stream<Item = Result<Bytes, PayloadError>>,
|
||||||
{
|
{
|
||||||
let status_code = response.status();
|
let status_code = response.status();
|
||||||
error!("Vault returned server error: {}", status_code);
|
|
||||||
let body = response.body().await.ok();
|
let body = response.body().await.ok();
|
||||||
let content_type = response.content_type().to_string();
|
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 {
|
Self::Proxy(ProxyResponseError {
|
||||||
status_code,
|
status_code,
|
||||||
body,
|
body,
|
||||||
|
|
|
@ -1,215 +1,283 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2023-2024 Matter Labs
|
// Copyright (c) 2023-2024 Matter Labs
|
||||||
|
|
||||||
//! Some cryptographic utilities
|
//! 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::{
|
pub use crate::sgx::{
|
||||||
parse_tcb_levels, sgx_ql_qv_result_t, verify_quote_with_collateral, EnumSet,
|
parse_tcb_levels, sgx_ql_qv_result_t, verify_quote_with_collateral, EnumSet,
|
||||||
QuoteVerificationResult, TcbLevel,
|
QuoteVerificationResult, TcbLevel,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use const_oid::db::rfc5280::{
|
use const_oid::db::rfc5280::{ID_KP_CLIENT_AUTH, ID_KP_SERVER_AUTH};
|
||||||
ID_CE_BASIC_CONSTRAINTS, ID_CE_EXT_KEY_USAGE, ID_CE_KEY_USAGE, ID_KP_CLIENT_AUTH,
|
use const_oid::AssociatedOid;
|
||||||
ID_KP_SERVER_AUTH,
|
|
||||||
};
|
|
||||||
use const_oid::db::rfc5912::SECP_256_R_1;
|
|
||||||
use getrandom::getrandom;
|
use getrandom::getrandom;
|
||||||
use pkcs8::der::asn1::OctetString;
|
use p256::ecdsa::DerSignature;
|
||||||
use pkcs8::der::referenced::OwnedToRef;
|
use p256::pkcs8::EncodePrivateKey;
|
||||||
use pkcs8::der::referenced::RefToOwned;
|
use pkcs8::der;
|
||||||
use pkcs8::{
|
|
||||||
AlgorithmIdentifierRef, ObjectIdentifier, PrivateKeyInfo, SubjectPublicKeyInfo,
|
|
||||||
SubjectPublicKeyInfoRef,
|
|
||||||
};
|
|
||||||
use rustls::pki_types::PrivatePkcs8KeyDer;
|
use rustls::pki_types::PrivatePkcs8KeyDer;
|
||||||
use sec1::EcPrivateKey;
|
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
use signature::Signer;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use x509_cert::der::asn1::BitString;
|
use tracing::debug;
|
||||||
use x509_cert::der::{Decode as _, Encode as _};
|
use x509_cert::builder::{Builder, CertificateBuilder, Profile};
|
||||||
use x509_cert::ext::pkix::{BasicConstraints, ExtendedKeyUsage, KeyUsage, KeyUsages};
|
use x509_cert::der::pem::LineEnding;
|
||||||
use x509_cert::name::RdnSequence;
|
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::serial_number::SerialNumber;
|
||||||
|
use x509_cert::spki::{
|
||||||
|
DynSignatureAlgorithmIdentifier, EncodePublicKey, ObjectIdentifier, SignatureBitStringEncoding,
|
||||||
|
SubjectPublicKeyInfoOwned,
|
||||||
|
};
|
||||||
use x509_cert::time::Validity;
|
use x509_cert::time::Validity;
|
||||||
use x509_cert::{Certificate, TbsCertificate};
|
use x509_cert::Certificate;
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
use const_oid::db::rfc5912::{
|
/// The OID for the `gramine-ra-tls` quote extension
|
||||||
ECDSA_WITH_SHA_256, ECDSA_WITH_SHA_384, ID_EC_PUBLIC_KEY as ECPK, SECP_256_R_1 as P256,
|
pub const ID_GRAMINE_RA_TLS_QUOTE: ObjectIdentifier =
|
||||||
SECP_384_R_1 as P384,
|
ObjectIdentifier::new_unwrap("1.2.840.113741.1337.6");
|
||||||
};
|
|
||||||
use pkcs8::der::asn1::BitStringRef;
|
|
||||||
|
|
||||||
const ES256: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef {
|
/// The OID for the `enarx-ra-tls` collateral extension
|
||||||
oid: ECDSA_WITH_SHA_256,
|
/// TODO: this OID is just made up in `enarx` OID namespace, reserve it somehow
|
||||||
parameters: None,
|
pub const ID_GRAMINE_RA_TLS_COLLATERAL: ObjectIdentifier =
|
||||||
};
|
ObjectIdentifier::new_unwrap("1.3.6.1.4.1.58270.1.99");
|
||||||
|
|
||||||
const ES384: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef {
|
/// The `gramine-ra-tls` x509 extension
|
||||||
oid: ECDSA_WITH_SHA_384,
|
pub struct RaTlsQuoteExtension {
|
||||||
parameters: None,
|
/// The hash of the certificate's public key
|
||||||
};
|
pub quote: Vec<u8>,
|
||||||
|
|
||||||
/// Utility trait for signing with a private key
|
|
||||||
pub trait PrivateKeyInfoExt {
|
|
||||||
/// Generates a keypair
|
|
||||||
///
|
|
||||||
/// Returns the DER encoding of the `PrivateKeyInfo` type.
|
|
||||||
fn generate(oid: ObjectIdentifier) -> Result<Zeroizing<Vec<u8>>>;
|
|
||||||
|
|
||||||
/// Get the public key
|
|
||||||
///
|
|
||||||
/// This function creates a `SubjectPublicKeyInfo` which corresponds with
|
|
||||||
/// this private key. Note that this function does not do any cryptographic
|
|
||||||
/// calculations. It expects that the `PrivateKeyInfo` already contains the
|
|
||||||
/// public key.
|
|
||||||
fn public_key(&self) -> Result<SubjectPublicKeyInfoRef<'_>>;
|
|
||||||
|
|
||||||
/// Get the default signing algorithm for this `SubjectPublicKeyInfo`
|
|
||||||
fn signs_with(&self) -> Result<AlgorithmIdentifierRef<'_>>;
|
|
||||||
|
|
||||||
/// Signs the body with the specified algorithm
|
|
||||||
///
|
|
||||||
/// Note that the signature is returned in its encoded form as it will
|
|
||||||
/// appear in an X.509 certificate or PKCS#10 certification request.
|
|
||||||
fn sign(&self, body: &[u8], algo: AlgorithmIdentifierRef<'_>) -> Result<Vec<u8>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PrivateKeyInfoExt for PrivateKeyInfo<'a> {
|
impl AssociatedOid for RaTlsQuoteExtension {
|
||||||
fn generate(oid: ObjectIdentifier) -> Result<Zeroizing<Vec<u8>>> {
|
const OID: ObjectIdentifier = ID_GRAMINE_RA_TLS_QUOTE;
|
||||||
let rand = ring::rand::SystemRandom::new();
|
|
||||||
|
|
||||||
let doc = match oid {
|
|
||||||
P256 => {
|
|
||||||
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_ASN1_SIGNING as ALG};
|
|
||||||
EcdsaKeyPair::generate_pkcs8(&ALG, &rand)?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
P384 => {
|
impl x509_cert::der::Encode for RaTlsQuoteExtension {
|
||||||
use ring::signature::{EcdsaKeyPair, ECDSA_P384_SHA384_ASN1_SIGNING as ALG};
|
fn encoded_len(&self) -> pkcs8::der::Result<Length> {
|
||||||
EcdsaKeyPair::generate_pkcs8(&ALG, &rand)?
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => return Err(anyhow!("unsupported")),
|
fn encode(
|
||||||
};
|
&self,
|
||||||
|
_writer: &mut impl x509_cert::der::Writer,
|
||||||
Ok(doc.as_ref().to_vec().into())
|
) -> Result<(), x509_cert::der::Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn public_key(&self) -> Result<SubjectPublicKeyInfoRef<'_>> {
|
impl AsExtension for RaTlsQuoteExtension {
|
||||||
match self.algorithm.oids()? {
|
fn critical(&self, _: &x509_cert::name::Name, _: &[x509_cert::ext::Extension]) -> bool {
|
||||||
(ECPK, ..) => {
|
false
|
||||||
let ec = EcPrivateKey::from_der(self.private_key)?;
|
}
|
||||||
let pk = ec.public_key.ok_or_else(|| anyhow!("missing public key"))?;
|
fn to_extension(
|
||||||
Ok(SubjectPublicKeyInfo {
|
&self,
|
||||||
algorithm: self.algorithm,
|
_subject: &Name,
|
||||||
subject_public_key: BitStringRef::new(0, pk)?,
|
_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())?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(anyhow!("unsupported")),
|
}
|
||||||
|
|
||||||
|
/// 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!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signs_with(&self) -> Result<AlgorithmIdentifierRef<'_>> {
|
impl AsExtension for RaTlsCollateralExtension {
|
||||||
match self.algorithm.oids()? {
|
fn critical(&self, _: &x509_cert::name::Name, _: &[x509_cert::ext::Extension]) -> bool {
|
||||||
(ECPK, Some(P256)) => Ok(ES256),
|
false
|
||||||
(ECPK, Some(P384)) => Ok(ES384),
|
|
||||||
_ => Err(anyhow!("unsupported")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sign(&self, body: &[u8], algo: AlgorithmIdentifierRef<'_>) -> Result<Vec<u8>> {
|
|
||||||
let rng = ring::rand::SystemRandom::new();
|
|
||||||
match (self.algorithm.oids()?, algo) {
|
|
||||||
((ECPK, Some(P256)), ES256) => {
|
|
||||||
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_ASN1_SIGNING as ALG};
|
|
||||||
let kp = EcdsaKeyPair::from_pkcs8(&ALG, &self.to_der()?, &rng)?;
|
|
||||||
Ok(kp.sign(&rng, body)?.as_ref().to_vec())
|
|
||||||
}
|
|
||||||
|
|
||||||
((ECPK, Some(P384)), ES384) => {
|
|
||||||
use ring::signature::{EcdsaKeyPair, ECDSA_P384_SHA384_ASN1_SIGNING as ALG};
|
|
||||||
let kp = EcdsaKeyPair::from_pkcs8(&ALG, &self.to_der()?, &rng)?;
|
|
||||||
Ok(kp.sign(&rng, body)?.as_ref().to_vec())
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => Err(anyhow!("unsupported")),
|
|
||||||
}
|
}
|
||||||
|
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
|
/// Create a private key and a self-signed certificate
|
||||||
pub fn make_self_signed_cert() -> Result<(
|
pub fn make_self_signed_cert(
|
||||||
|
dn: &str,
|
||||||
|
an: Option<GeneralNames>,
|
||||||
|
) -> Result<(
|
||||||
[u8; 64],
|
[u8; 64],
|
||||||
rustls::pki_types::CertificateDer<'static>,
|
rustls::pki_types::CertificateDer<'static>,
|
||||||
rustls::pki_types::PrivateKeyDer<'static>,
|
rustls::pki_types::PrivateKeyDer<'static>,
|
||||||
)> {
|
)> {
|
||||||
// Generate a keypair.
|
// Generate a keypair.
|
||||||
let raw = PrivateKeyInfo::generate(SECP_256_R_1).context("failed to generate a private key")?;
|
let mut rng = rand::thread_rng();
|
||||||
let pki = PrivateKeyInfo::from_der(raw.as_ref())
|
let signing_key = p256::ecdsa::SigningKey::random(&mut rng);
|
||||||
.context("failed to parse DER-encoded private key")?;
|
let verifying_key = signing_key.verifying_key();
|
||||||
let der = pki.public_key().unwrap().to_der().unwrap();
|
let verifying_key_der = verifying_key
|
||||||
|
.to_public_key_der()
|
||||||
|
.context("failed to create public key der")?;
|
||||||
|
|
||||||
let mut key_hash = [0u8; 64];
|
let mut key_hash = [0u8; 64];
|
||||||
let hash = Sha256::digest(der);
|
let hash = Sha256::digest(verifying_key_der.as_bytes());
|
||||||
key_hash[..32].copy_from_slice(&hash);
|
key_hash[..32].copy_from_slice(&hash);
|
||||||
|
|
||||||
|
let quote = get_quote(&key_hash)?;
|
||||||
|
debug!("quote.len: {:?}", quote.len());
|
||||||
// Create a relative distinguished name.
|
// Create a relative distinguished name.
|
||||||
let rdns = RdnSequence::from_str("CN=localhost")?;
|
let rdns = RdnSequence::from_str(dn)?;
|
||||||
|
let collateral = tee_qv_get_collateral("e).context("Failed to get own collateral")?;
|
||||||
// Create the extensions.
|
|
||||||
let ku = KeyUsage(KeyUsages::DigitalSignature | KeyUsages::KeyEncipherment).to_der()?;
|
|
||||||
let eu = ExtendedKeyUsage(vec![ID_KP_SERVER_AUTH, ID_KP_CLIENT_AUTH]).to_der()?;
|
|
||||||
let bc = BasicConstraints {
|
|
||||||
ca: false,
|
|
||||||
path_len_constraint: None,
|
|
||||||
}
|
|
||||||
.to_der()?;
|
|
||||||
|
|
||||||
let mut serial = [0u8; 16];
|
let mut serial = [0u8; 16];
|
||||||
getrandom(&mut serial)?;
|
getrandom(&mut serial)?;
|
||||||
|
|
||||||
// Create the certificate body.
|
let mut builder = CertificateBuilder::new(
|
||||||
let tbs = TbsCertificate {
|
Profile::Leaf {
|
||||||
version: x509_cert::Version::V3,
|
|
||||||
serial_number: SerialNumber::new(&serial)?,
|
|
||||||
signature: pki.signs_with()?.ref_to_owned(),
|
|
||||||
issuer: rdns.clone(),
|
issuer: rdns.clone(),
|
||||||
validity: Validity::from_now(Duration::from_secs(60 * 60 * 24 * 365))?,
|
enable_key_agreement: true,
|
||||||
subject: rdns,
|
enable_key_encipherment: true,
|
||||||
subject_public_key_info: pki.public_key()?.ref_to_owned(),
|
|
||||||
issuer_unique_id: None,
|
|
||||||
subject_unique_id: None,
|
|
||||||
extensions: Some(vec![
|
|
||||||
x509_cert::ext::Extension {
|
|
||||||
extn_id: ID_CE_KEY_USAGE,
|
|
||||||
critical: true,
|
|
||||||
extn_value: OctetString::new(ku)?,
|
|
||||||
},
|
},
|
||||||
x509_cert::ext::Extension {
|
SerialNumber::new(&serial)?,
|
||||||
extn_id: ID_CE_BASIC_CONSTRAINTS,
|
Validity::from_now(Duration::from_secs(60 * 60 * 24 * 365 * 10))?,
|
||||||
critical: true,
|
rdns,
|
||||||
extn_value: OctetString::new(bc)?,
|
SubjectPublicKeyInfoOwned::try_from(verifying_key_der.as_bytes())
|
||||||
},
|
.context("failed to create SubjectPublicKeyInfo")?,
|
||||||
x509_cert::ext::Extension {
|
&signing_key,
|
||||||
extn_id: ID_CE_EXT_KEY_USAGE,
|
)
|
||||||
critical: false,
|
.context("failed to create CertificateBuilder")?;
|
||||||
extn_value: OctetString::new(eu)?,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Self-sign the certificate.
|
builder
|
||||||
let alg = tbs.signature.clone();
|
.add_extension(&ExtendedKeyUsage(vec![
|
||||||
let sig = pki.sign(&tbs.to_der()?, alg.owned_to_ref())?;
|
ID_KP_SERVER_AUTH,
|
||||||
let crt = Certificate {
|
ID_KP_CLIENT_AUTH,
|
||||||
tbs_certificate: tbs,
|
]))
|
||||||
signature_algorithm: alg,
|
.context("failed to add ExtendedKeyUsage")?;
|
||||||
signature: BitString::from_bytes(&sig)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
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 rustls_certificate = rustls::pki_types::CertificateDer::from(crt.to_der()?);
|
||||||
let rustls_pk = rustls::pki_types::PrivateKeyDer::from(PrivatePkcs8KeyDer::from(pki.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))
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// Copyright (c) 2023 Matter Labs
|
// Copyright (c) 2023-2024 Matter Labs
|
||||||
|
|
||||||
// Copyright (c) The Enarx Project Developers https://github.com/enarx/sgx
|
// Copyright (c) The Enarx Project Developers https://github.com/enarx/sgx
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ pub struct QuoteVerificationResult<'a> {
|
||||||
pub quote: &'a Quote,
|
pub quote: &'a Quote,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies a quote with collateral material
|
/// Verifies a quote with optional collateral material
|
||||||
pub fn verify_quote_with_collateral<'a>(
|
pub fn verify_quote_with_collateral<'a>(
|
||||||
quote: &'a [u8],
|
quote: &'a [u8],
|
||||||
collateral: Option<&Collateral>,
|
collateral: Option<&Collateral>,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright (c) 2024 Matter Labs
|
||||||
|
|
||||||
mod sgx {
|
mod sgx {
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
@ -23,7 +24,7 @@ mod sgx {
|
||||||
tcb_level_date_tag,
|
tcb_level_date_tag,
|
||||||
} = verify_quote_with_collateral(quote, Some(collateral), current_time).unwrap();
|
} = verify_quote_with_collateral(quote, Some(collateral), current_time).unwrap();
|
||||||
|
|
||||||
if collateral_expired || !matches!(result, sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK) {
|
if collateral_expired || result != sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK {
|
||||||
print!("Attestation failed: ");
|
print!("Attestation failed: ");
|
||||||
|
|
||||||
if collateral_expired {
|
if collateral_expired {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue