mirror of
https://github.com/matter-labs/teepot.git
synced 2025-07-22 07:24:48 +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
|
@ -8,26 +8,27 @@
|
|||
|
||||
pub mod vault;
|
||||
|
||||
use crate::json::http::AttestationResponse;
|
||||
use crate::sgx::Collateral;
|
||||
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::{anyhow, bail, Context, Result};
|
||||
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 serde_json::Value;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
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::Certificate;
|
||||
|
||||
|
@ -61,13 +62,11 @@ impl TeeConnection {
|
|||
///
|
||||
/// This will verify the attestation report and check that the enclave
|
||||
/// is running the expected code.
|
||||
pub async fn new(args: &AttestationArgs, attestation_url: &str) -> Result<Self> {
|
||||
let pk_hash = Arc::new(OnceLock::new());
|
||||
|
||||
pub fn new(args: &AttestationArgs) -> Self {
|
||||
let tls_config = Arc::new(
|
||||
ClientConfig::builder()
|
||||
.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(),
|
||||
);
|
||||
|
||||
|
@ -78,15 +77,10 @@ impl TeeConnection {
|
|||
.timeout(Duration::from_secs(12000))
|
||||
.finish();
|
||||
|
||||
let this = Self {
|
||||
Self {
|
||||
server: args.server.clone(),
|
||||
client: agent,
|
||||
};
|
||||
|
||||
this.check_attestation(args, attestation_url, pk_hash)
|
||||
.await?;
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new connection to a TEE
|
||||
|
@ -110,131 +104,12 @@ impl TeeConnection {
|
|||
&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
|
||||
/// 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)]
|
||||
struct V {
|
||||
pk_hash: Arc<OnceLock<[u8; 32]>>,
|
||||
args: AttestationArgs,
|
||||
server_verifier: Arc<WebPkiServerVerifier>,
|
||||
}
|
||||
impl ServerCertVerifier for V {
|
||||
|
@ -255,20 +130,133 @@ impl TeeConnection {
|
|||
.unwrap();
|
||||
|
||||
let hash = Sha256::digest(pub_key);
|
||||
let data = self.pk_hash.get_or_init(|| hash[..32].try_into().unwrap());
|
||||
|
||||
if data == &hash[..32] {
|
||||
info!(
|
||||
"Checked or set server certificate public key hash `{}`",
|
||||
hex::encode(&hash[..32])
|
||||
);
|
||||
Ok(rustls::client::danger::ServerCertVerified::assertion())
|
||||
} else {
|
||||
error!("Server certificate does not match expected certificate");
|
||||
Err(rustls::Error::General(
|
||||
"Server certificate does not match expected certificate".to_string(),
|
||||
))
|
||||
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(
|
||||
|
@ -295,10 +283,14 @@ impl TeeConnection {
|
|||
self.server_verifier.supported_verify_schemes()
|
||||
}
|
||||
}
|
||||
let root_store = Arc::new(rustls::RootCertStore::empty());
|
||||
let server_verifier = WebPkiServerVerifier::builder(root_store).build().unwrap();
|
||||
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 {
|
||||
pk_hash,
|
||||
args,
|
||||
server_verifier,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// 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.
|
||||
|
||||
|
@ -22,11 +22,10 @@ use awc::error::{SendRequestError, StatusCode};
|
|||
use awc::{Client, ClientResponse, Connector};
|
||||
use bytes::Bytes;
|
||||
use futures_core::Stream;
|
||||
use getrandom::getrandom;
|
||||
use rustls::ClientConfig;
|
||||
use serde_json::{json, Value};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use tracing::{debug, error, info, trace};
|
||||
|
||||
|
@ -85,15 +84,14 @@ impl VaultConnection {
|
|||
/// 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 pk_hash = Arc::new(OnceLock::new());
|
||||
|
||||
let (key_hash, rustls_certificate, rustls_pk) = make_self_signed_cert()?;
|
||||
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(
|
||||
pk_hash.clone(),
|
||||
args.clone(),
|
||||
)))
|
||||
.with_client_auth_cert(vec![rustls_certificate], rustls_pk)?,
|
||||
);
|
||||
|
@ -114,7 +112,7 @@ impl VaultConnection {
|
|||
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);
|
||||
|
||||
|
@ -147,24 +145,18 @@ impl VaultConnection {
|
|||
self.conn.client()
|
||||
}
|
||||
|
||||
async fn auth(&self, args: &AttestationArgs) -> Result<AuthResponse> {
|
||||
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 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 {
|
||||
name: self.name.clone(),
|
||||
tee_type: "sgx".to_string(),
|
||||
quote,
|
||||
collateral: serde_json::to_string(&collateral)?,
|
||||
challenge,
|
||||
challenge: None,
|
||||
};
|
||||
|
||||
let mut response = self
|
||||
|
@ -197,26 +189,6 @@ impl VaultConnection {
|
|||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -306,7 +278,10 @@ impl VaultConnection {
|
|||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// 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
|
||||
|
||||
|
@ -23,9 +23,6 @@ impl Unseal {
|
|||
pub const URL: &'static str = "/v1/sys/unseal";
|
||||
}
|
||||
|
||||
/// The attestation URL
|
||||
pub const ATTESTATION_URL: &str = "/v1/sys/attestation";
|
||||
|
||||
/// The attestation response
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AttestationResponse {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
//! Common attestation API for all TEEs
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
//! # tee-server
|
||||
|
||||
|
@ -78,9 +78,15 @@ impl HttpResponseError {
|
|||
S: Stream<Item = Result<Bytes, PayloadError>>,
|
||||
{
|
||||
let status_code = response.status();
|
||||
error!("Vault returned server error: {}", status_code);
|
||||
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,
|
||||
|
|
|
@ -1,215 +1,283 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// 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::{
|
||||
parse_tcb_levels, sgx_ql_qv_result_t, verify_quote_with_collateral, EnumSet,
|
||||
QuoteVerificationResult, TcbLevel,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use const_oid::db::rfc5280::{
|
||||
ID_CE_BASIC_CONSTRAINTS, ID_CE_EXT_KEY_USAGE, ID_CE_KEY_USAGE, ID_KP_CLIENT_AUTH,
|
||||
ID_KP_SERVER_AUTH,
|
||||
};
|
||||
use const_oid::db::rfc5912::SECP_256_R_1;
|
||||
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 pkcs8::der::asn1::OctetString;
|
||||
use pkcs8::der::referenced::OwnedToRef;
|
||||
use pkcs8::der::referenced::RefToOwned;
|
||||
use pkcs8::{
|
||||
AlgorithmIdentifierRef, ObjectIdentifier, PrivateKeyInfo, SubjectPublicKeyInfo,
|
||||
SubjectPublicKeyInfoRef,
|
||||
};
|
||||
use p256::ecdsa::DerSignature;
|
||||
use p256::pkcs8::EncodePrivateKey;
|
||||
use pkcs8::der;
|
||||
use rustls::pki_types::PrivatePkcs8KeyDer;
|
||||
use sec1::EcPrivateKey;
|
||||
use sha2::{Digest, Sha256};
|
||||
use signature::Signer;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use x509_cert::der::asn1::BitString;
|
||||
use x509_cert::der::{Decode as _, Encode as _};
|
||||
use x509_cert::ext::pkix::{BasicConstraints, ExtendedKeyUsage, KeyUsage, KeyUsages};
|
||||
use x509_cert::name::RdnSequence;
|
||||
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, TbsCertificate};
|
||||
use x509_cert::Certificate;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use const_oid::db::rfc5912::{
|
||||
ECDSA_WITH_SHA_256, ECDSA_WITH_SHA_384, ID_EC_PUBLIC_KEY as ECPK, SECP_256_R_1 as P256,
|
||||
SECP_384_R_1 as P384,
|
||||
};
|
||||
use pkcs8::der::asn1::BitStringRef;
|
||||
/// 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");
|
||||
|
||||
const ES256: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef {
|
||||
oid: ECDSA_WITH_SHA_256,
|
||||
parameters: None,
|
||||
};
|
||||
/// 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");
|
||||
|
||||
const ES384: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef {
|
||||
oid: ECDSA_WITH_SHA_384,
|
||||
parameters: None,
|
||||
};
|
||||
|
||||
/// 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>>;
|
||||
/// The `gramine-ra-tls` x509 extension
|
||||
pub struct RaTlsQuoteExtension {
|
||||
/// The hash of the certificate's public key
|
||||
pub quote: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> PrivateKeyInfoExt for PrivateKeyInfo<'a> {
|
||||
fn generate(oid: ObjectIdentifier) -> Result<Zeroizing<Vec<u8>>> {
|
||||
let rand = ring::rand::SystemRandom::new();
|
||||
impl AssociatedOid for RaTlsQuoteExtension {
|
||||
const OID: ObjectIdentifier = ID_GRAMINE_RA_TLS_QUOTE;
|
||||
}
|
||||
|
||||
let doc = match oid {
|
||||
P256 => {
|
||||
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_ASN1_SIGNING as ALG};
|
||||
EcdsaKeyPair::generate_pkcs8(&ALG, &rand)?
|
||||
}
|
||||
|
||||
P384 => {
|
||||
use ring::signature::{EcdsaKeyPair, ECDSA_P384_SHA384_ASN1_SIGNING as ALG};
|
||||
EcdsaKeyPair::generate_pkcs8(&ALG, &rand)?
|
||||
}
|
||||
|
||||
_ => return Err(anyhow!("unsupported")),
|
||||
};
|
||||
|
||||
Ok(doc.as_ref().to_vec().into())
|
||||
impl x509_cert::der::Encode for RaTlsQuoteExtension {
|
||||
fn encoded_len(&self) -> pkcs8::der::Result<Length> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn public_key(&self) -> Result<SubjectPublicKeyInfoRef<'_>> {
|
||||
match self.algorithm.oids()? {
|
||||
(ECPK, ..) => {
|
||||
let ec = EcPrivateKey::from_der(self.private_key)?;
|
||||
let pk = ec.public_key.ok_or_else(|| anyhow!("missing public key"))?;
|
||||
Ok(SubjectPublicKeyInfo {
|
||||
algorithm: self.algorithm,
|
||||
subject_public_key: BitStringRef::new(0, pk)?,
|
||||
})
|
||||
}
|
||||
_ => Err(anyhow!("unsupported")),
|
||||
}
|
||||
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 signs_with(&self) -> Result<AlgorithmIdentifierRef<'_>> {
|
||||
match self.algorithm.oids()? {
|
||||
(ECPK, Some(P256)) => Ok(ES256),
|
||||
(ECPK, Some(P384)) => Ok(ES384),
|
||||
_ => Err(anyhow!("unsupported")),
|
||||
}
|
||||
fn encode(
|
||||
&self,
|
||||
_writer: &mut impl x509_cert::der::Writer,
|
||||
) -> Result<(), x509_cert::der::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
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")),
|
||||
}
|
||||
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() -> Result<(
|
||||
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 raw = PrivateKeyInfo::generate(SECP_256_R_1).context("failed to generate a private key")?;
|
||||
let pki = PrivateKeyInfo::from_der(raw.as_ref())
|
||||
.context("failed to parse DER-encoded private key")?;
|
||||
let der = pki.public_key().unwrap().to_der().unwrap();
|
||||
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(der);
|
||||
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("CN=localhost")?;
|
||||
|
||||
// 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 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)?;
|
||||
|
||||
// Create the certificate body.
|
||||
let tbs = TbsCertificate {
|
||||
version: x509_cert::Version::V3,
|
||||
serial_number: SerialNumber::new(&serial)?,
|
||||
signature: pki.signs_with()?.ref_to_owned(),
|
||||
issuer: rdns.clone(),
|
||||
validity: Validity::from_now(Duration::from_secs(60 * 60 * 24 * 365))?,
|
||||
subject: rdns,
|
||||
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 {
|
||||
extn_id: ID_CE_BASIC_CONSTRAINTS,
|
||||
critical: true,
|
||||
extn_value: OctetString::new(bc)?,
|
||||
},
|
||||
x509_cert::ext::Extension {
|
||||
extn_id: ID_CE_EXT_KEY_USAGE,
|
||||
critical: false,
|
||||
extn_value: OctetString::new(eu)?,
|
||||
},
|
||||
]),
|
||||
};
|
||||
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")?;
|
||||
|
||||
// Self-sign the certificate.
|
||||
let alg = tbs.signature.clone();
|
||||
let sig = pki.sign(&tbs.to_der()?, alg.owned_to_ref())?;
|
||||
let crt = Certificate {
|
||||
tbs_certificate: tbs,
|
||||
signature_algorithm: alg,
|
||||
signature: BitString::from_bytes(&sig)?,
|
||||
};
|
||||
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 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))
|
||||
}
|
||||
|
||||
/// 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
|
||||
// Copyright (c) 2023 Matter Labs
|
||||
// Copyright (c) 2023-2024 Matter Labs
|
||||
|
||||
// Copyright (c) The Enarx Project Developers https://github.com/enarx/sgx
|
||||
|
||||
|
@ -114,7 +114,7 @@ pub struct QuoteVerificationResult<'a> {
|
|||
pub quote: &'a Quote,
|
||||
}
|
||||
|
||||
/// Verifies a quote with collateral material
|
||||
/// Verifies a quote with optional collateral material
|
||||
pub fn verify_quote_with_collateral<'a>(
|
||||
quote: &'a [u8],
|
||||
collateral: Option<&Collateral>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue