Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: intel sgx attestation #630

Merged
merged 14 commits into from
Oct 28, 2024
8 changes: 8 additions & 0 deletions crates/notary/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name = "notary-server"
version = "0.1.0-alpha.7"
edition = "2021"

[features]
tee_quote = ["dep:mc-sgx-dcap-types", "dep:hex", "dep:rand_chacha", "dep:once_cell"]

[dependencies]
tlsn-core = { workspace = true }
tlsn-common = { workspace = true }
Expand Down Expand Up @@ -49,3 +52,8 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] }
uuid = { workspace = true, features = ["v4", "fast-rng"] }
ws_stream_tungstenite = { workspace = true, features = ["tokio_io"] }
zeroize = { workspace = true }

mc-sgx-dcap-types = { version = "0.11.0", optional = true }
hex = { workspace = true, optional = true }
rand_chacha = { workspace = true, optional = true }
once_cell = { workspace = true, optional =true }
35 changes: 20 additions & 15 deletions crates/notary/server/build.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
use std::process::Command;
use std::{env, process::Command};

fn main() {
// Used to extract latest HEAD commit hash and timestamp for the /info endpoint
let output = Command::new("git")
.args(["show", "HEAD", "-s", "--format=%H,%cI"])
.output()
.expect("Git command to get commit hash and timestamp should work during build process");
if env::var("GIT_COMMIT_HASH").is_ok() && env::var("GIT_COMMIT_TIMESTAMP").is_ok() {
} else {
maceip marked this conversation as resolved.
Show resolved Hide resolved
// Used to extract latest HEAD commit hash and timestamp for the /info endpoint
maceip marked this conversation as resolved.
Show resolved Hide resolved
let output = Command::new("git")
.args(["show", "HEAD", "-s", "--format=%H,%cI"])
.output()
.expect(
"Git command to get commit hash and timestamp should work during build process",
);

let output_string =
String::from_utf8(output.stdout).expect("Git command should produce valid string output");
let output_string = String::from_utf8(output.stdout)
.expect("Git command should produce valid string output");

let (commit_hash, commit_timestamp) = output_string
.as_str()
.split_once(',')
.expect("Git commit hash and timestamp string output should be comma separated");
let (commit_hash, commit_timestamp) = output_string
.as_str()
.split_once(',')
.expect("Git commit hash and timestamp string output should be comma separated");

// Pass these 2 values as env var to the program
println!("cargo:rustc-env=GIT_COMMIT_HASH={}", commit_hash);
println!("cargo:rustc-env=GIT_COMMIT_TIMESTAMP={}", commit_timestamp);
// Pass these 2 values as env var to the program
println!("cargo:rustc-env=GIT_COMMIT_HASH={}", commit_hash);
println!("cargo:rustc-env=GIT_COMMIT_TIMESTAMP={}", commit_timestamp);
}
}
6 changes: 5 additions & 1 deletion crates/notary/server/src/domain.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
pub mod auth;
pub mod cli;
pub mod notary;

#[cfg(feature = "tee_quote")]
use crate::tee::Quote;
use serde::{Deserialize, Serialize};

/// Response object of the /info API
Expand All @@ -16,4 +17,7 @@ pub struct InfoResponse {
pub git_commit_hash: String,
/// Current git commit timestamp of notary-server
pub git_commit_timestamp: String,
/// Hardware attestation
#[cfg(feature = "tee_quote")]
pub quote: Quote,
}
2 changes: 2 additions & 0 deletions crates/notary/server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ mod server;
mod server_tracing;
mod service;
mod signing;
#[cfg(feature = "tee_quote")]
mod tee;
mod util;

pub use config::{
Expand Down
13 changes: 13 additions & 0 deletions crates/notary/server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,19 @@
util::parse_csv_file,
};

#[cfg(feature = "tee_quote")]
use crate::tee::{ephemeral_keypair, quote};

/// Start a TCP server (with or without TLS) to accept notarization request for
/// both TCP and WebSocket clients
#[tracing::instrument(skip(config))]
pub async fn run_server(config: &NotaryServerProperties) -> Result<(), NotaryServerError> {
// tee uses ephemeral key
#[cfg(feature = "tee_quote")]
let (attestation_key, public_key) = ephemeral_keypair();
maceip marked this conversation as resolved.
Show resolved Hide resolved

// Load the private key for notarized transcript signing
#[cfg(not(feature = "tee_quote"))]
let attestation_key = load_attestation_key(&config.notary_key).await?;
let crypto_provider = build_crypto_provider(attestation_key);

Expand Down Expand Up @@ -107,8 +115,10 @@
);

// Parameters needed for the info endpoint
#[cfg(not(feature = "tee_quote"))]
let public_key = std::fs::read_to_string(&config.notary_key.public_key_pem_path)
.map_err(|err| eyre!("Failed to load notary public signing key for notarization: {err}"))?;

let version = env!("CARGO_PKG_VERSION").to_string();
let git_commit_hash = env!("GIT_COMMIT_HASH").to_string();
let git_commit_timestamp = env!("GIT_COMMIT_TIMESTAMP").to_string();
Expand Down Expand Up @@ -142,6 +152,8 @@
public_key,
git_commit_hash,
git_commit_timestamp,
#[cfg(feature = "tee_quote")]
quote: quote().await,

Check warning on line 156 in crates/notary/server/src/server.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/server.rs#L155-L156

Added lines #L155 - L156 were not covered by tests
}),
)
.into_response()
Expand Down Expand Up @@ -231,6 +243,7 @@
}

/// Load notary signing key for attestations from static file
#[allow(dead_code)]
async fn load_attestation_key(config: &NotarySigningKeyProperties) -> Result<AttestationKey> {
debug!("Loading notary server's signing key");

Expand Down
174 changes: 174 additions & 0 deletions crates/notary/server/src/tee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use base64::{engine::general_purpose::STANDARD, Engine};
use mc_sgx_dcap_types::QlError;
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
use std::fs;

use crate::signing::AttestationKey;
use k256::ecdsa::{SigningKey, VerifyingKey as PublicKey};
use pkcs8::{DecodePrivateKey, EncodePrivateKey, LineEnding};
use rand_chacha::{
rand_core::{OsRng, SeedableRng},
ChaCha20Rng,
};
use std::{
fs::File,
io::{self, Read},
path::Path,
};
use tracing::{debug, error, instrument};

#[derive(Debug, Clone, Serialize, Deserialize)]

Check warning on line 21 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L21

Added line #L21 was not covered by tests
#[serde(rename_all = "camelCase")]
pub struct Quote {
raw_quote: Option<String>,
mrsigner: Option<String>,
mrenclave: Option<String>,
error: Option<String>,
}

impl Default for Quote {
fn default() -> Quote {
Quote {
raw_quote: Some("".to_string()),
mrsigner: None,
mrenclave: None,
error: None,
}
}

Check warning on line 38 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L31-L38

Added lines #L31 - L38 were not covered by tests
}

impl std::fmt::Debug for QuoteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
QuoteError::IoError(err) => write!(f, "IoError: {:?}", err),
QuoteError::IntelQuoteLibrary(err) => {
write!(f, "IntelQuoteLibrary: {}", err)

Check warning on line 46 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L42-L46

Added lines #L42 - L46 were not covered by tests
}
}
}

Check warning on line 49 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L49

Added line #L49 was not covered by tests
}

impl From<io::Error> for QuoteError {
fn from(err: io::Error) -> QuoteError {
QuoteError::IoError(err)
}

Check warning on line 55 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L53-L55

Added lines #L53 - L55 were not covered by tests
}

enum QuoteError {
IoError(io::Error),
IntelQuoteLibrary(QlError),
}

impl From<QlError> for QuoteError {
fn from(src: QlError) -> Self {
Self::IntelQuoteLibrary(src)
}

Check warning on line 66 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L64-L66

Added lines #L64 - L66 were not covered by tests
}

static PUBLIC_KEY: OnceCell<PublicKey> = OnceCell::new();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sinui0 i wanted to make it explicit that the key can only be set once. so if someone figured out a way to exec the calling function again it couldnt be reset. not sure if thats possible or even if oncecell is the right way to do it


#[instrument(level = "debug", skip_all)]

Check warning on line 71 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L71

Added line #L71 was not covered by tests
async fn gramine_quote() -> Result<Quote, QuoteError> {
//// Check if the the gramine pseudo-hardware exists
if !Path::new("/dev/attestation/quote").exists() {
Quote::default();
yuroitaki marked this conversation as resolved.
Show resolved Hide resolved
}

// Reading attestation type
let mut attestation_file = File::open("/dev/attestation/attestation_type")?;
let mut attestation_type = String::new();
attestation_file.read_to_string(&mut attestation_type)?;
debug!("Detected attestation type: {}", attestation_type);

// Read `/dev/attestation/my_target_info`
let my_target_info = fs::read("/dev/attestation/my_target_info")?;

// Write to `/dev/attestation/target_info`
fs::write("/dev/attestation/target_info", my_target_info)?;

//// Writing the pubkey to bind the instance to the hw (note: this is not
//// mrsigner)
fs::write(
"/dev/attestation/user_report_data",
PUBLIC_KEY
.get()
.expect("pub_key_get")
.to_encoded_point(true)
.as_bytes(),
)?;

//// Reading from the gramine quote pseudo-hardware `/dev/attestation/quote`
let mut quote_file = File::open("/dev/attestation/quote")?;
let mut quote = Vec::new();
quote_file.read_to_end(&mut quote)?;
yuroitaki marked this conversation as resolved.
Show resolved Hide resolved

if quote.len() < 432 {
maceip marked this conversation as resolved.
Show resolved Hide resolved
error!("Quote data is too short, expected at least 432 bytes");
return Err(QuoteError::IntelQuoteLibrary(QlError::InvalidReport));
}

//// Extract mrenclave: enclave image, and mrsigner: identity key bound to
//// enclave https://github.com/intel/linux-sgx/blob/main/common/inc/sgx_quote.h
let mrenclave = hex::encode(&quote[112..144]);
yuroitaki marked this conversation as resolved.
Show resolved Hide resolved
let mrsigner = hex::encode(&quote[176..208]);

debug!("mrenclave: {}", mrenclave);
debug!("mrsigner: {}", mrsigner);

//// Return the Quote struct with the extracted data
Ok(Quote {
raw_quote: Some(hex::encode(quote)),
mrsigner: Some(mrsigner),
mrenclave: Some(mrenclave),
error: None,
})
}

pub fn ephemeral_keypair() -> (AttestationKey, String) {
let mut rng = ChaCha20Rng::from_rng(OsRng).expect("os rng err!");
let signing_key = SigningKey::random(&mut rng);
let pem_string = signing_key
.clone()
.to_pkcs8_pem(LineEnding::LF)
.expect("to pem");
let attkey = AttestationKey::from_pkcs8_pem(&pem_string).expect("from pem");
let derk = signing_key
.verifying_key()
.to_encoded_point(true)
.to_bytes();
let b64k = STANDARD.encode(derk.as_ref());
let pem = format!(
"-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----\n",
b64k
);

let _ = PUBLIC_KEY
.set(*signing_key.verifying_key())
.map_err(|_| "Public key has already been set");
(attkey, pem)
}

Check warning on line 150 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L128-L150

Added lines #L128 - L150 were not covered by tests

pub async fn quote() -> Quote {
//// tee-detection logic will live here, for now its only gramine-sgx
match gramine_quote().await {
Ok(quote) => quote,
Err(err) => {
error!("Failed to retrieve quote: {:?}", err);
match err {
QuoteError::IoError(_) => Quote {
raw_quote: None,
mrsigner: None,
mrenclave: None,
error: Some("io".to_owned()),
},
QuoteError::IntelQuoteLibrary(_) => Quote {
raw_quote: None,
mrsigner: None,
mrenclave: None,
error: Some("hw".to_owned()),
},

Check warning on line 170 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L152-L170

Added lines #L152 - L170 were not covered by tests
}
}
}
}

Check warning on line 174 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L174

Added line #L174 was not covered by tests
25 changes: 25 additions & 0 deletions crates/notary/server/tee/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#tlsnotary server for testing <> gramine sgx (gramine1.7, g++13, libiomp off :()
### notaryserverbuilds.azurecr.io/prod/notary-sgx

FROM notaryserverbuilds.azurecr.io/prod/gramine AS teesdk

ARG TOOLCHAIN=1.81.0
ENV PATH=/root/.cargo/bin:/usr/local/musl/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

RUN set -eux \
&& curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain=$TOOLCHAIN \
&& rustup target add \
x86_64-unknown-linux-gnu


RUN apt update && apt install -y libssl-dev libclang-dev
ARG TLSN_TAG=quote-presentation
yuroitaki marked this conversation as resolved.
Show resolved Hide resolved
ARG TLSN_FT=tee_quote
RUN git clone --depth 1 -b $TLSN_TAG https://github.com/tlsnotary/tlsn /tlsn && \
cargo build --release --bin notary-server --features $TLSN_FT --color always --manifest-path /tlsn/Cargo.toml
RUN cd tlsn/crates/notary/server/tee && gramine-sgx-gen-private-key && SGX=1 make

FROM notaryserverbuilds.azurecr.io/prod/gramine AS teetime
WORKDIR /tee
COPY --from=teesdk tlsn/crates/notary/server/tee .
ENTRYPOINT ["gramine-sgx", "notary-server"]
Loading