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
20 changes: 20 additions & 0 deletions crates/notary/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ name = "notary-server"
version = "0.1.0-alpha.8-pre"
edition = "2021"

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

[dependencies]
tlsn-core = { workspace = true }
tlsn-common = { workspace = true }
Expand Down Expand Up @@ -46,6 +57,15 @@ 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 }
simple_asn1 = {version = "0.6.2", optional = true }
pem = { version = "1.1.0", optional = true }
lazy_static = { version = "1.4", optional = true }

[build-dependencies]
git2 = "0.19.0"
chrono.workspace = true

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 @@ -14,4 +15,7 @@ pub struct InfoResponse {
pub public_key: String,
/// Current git commit hash of notary-server
pub git_commit_hash: 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 @@ -7,6 +7,8 @@ mod server_tracing;
mod service;
mod settings;
mod signing;
#[cfg(feature = "tee_quote")]
mod tee;
mod util;

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

#[cfg(feature = "tee_quote")]
use crate::tee::{generate_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> {
// Load the private key for notarized transcript signing
let attestation_key = load_attestation_key(&config.notary_key).await?;
let crypto_provider = build_crypto_provider(attestation_key);

Expand Down Expand Up @@ -139,6 +141,8 @@
version,
public_key,
git_commit_hash,
#[cfg(feature = "tee_quote")]
quote: quote().await,

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

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/server.rs#L144-L145

Added lines #L144 - L145 were not covered by tests
}),
)
.into_response()
Expand Down Expand Up @@ -229,6 +233,9 @@

/// Load notary signing key for attestations from static file
async fn load_attestation_key(config: &NotarySigningKeyProperties) -> Result<AttestationKey> {
#[cfg(feature = "tee_quote")]
generate_ephemeral_keypair(&config.private_key_pem_path, &config.public_key_pem_path);

debug!("Loading notary server's signing key");

let mut file = File::open(&config.private_key_pem_path).await?;
Expand Down
186 changes: 186 additions & 0 deletions crates/notary/server/src/tee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
use k256::ecdsa::{SigningKey, VerifyingKey as PublicKey};
use mc_sgx_dcap_types::{QlError, Quote3};
use once_cell::sync::OnceCell;
use pkcs8::{EncodePrivateKey, LineEnding};
use rand_chacha::{
rand_core::{OsRng, SeedableRng},
ChaCha20Rng,
};
use serde::{Deserialize, Serialize};
use std::{
fs,
fs::File,
io::{self, Read},
path::Path,
};
use tracing::{debug, error, instrument};

lazy_static::lazy_static! {
static ref SECP256K1_OID: simple_asn1::OID = simple_asn1::oid!(1, 3, 132, 0, 10);
static ref ECDSA_OID: simple_asn1::OID = simple_asn1::oid!(1, 2, 840, 10045, 2, 1);
}

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L23 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 40 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L33-L40

Added lines #L33 - L40 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 48 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L44-L48

Added lines #L44 - L48 were not covered by tests
}
}
}

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L51 was not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L55 - L57 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 68 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L66 - L68 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


fn pem_der_encode_with_asn1(public_point: &[u8]) -> String {
use simple_asn1::*;

let ecdsa_oid = ASN1Block::ObjectIdentifier(0, ECDSA_OID.clone());
let secp256k1_oid = ASN1Block::ObjectIdentifier(0, SECP256K1_OID.clone());
let alg_id = ASN1Block::Sequence(0, vec![ecdsa_oid, secp256k1_oid]);
let key_bytes = ASN1Block::BitString(0, public_point.len() * 8, public_point.to_vec());

let blocks = vec![alg_id, key_bytes];

let der_out = simple_asn1::to_der(&ASN1Block::Sequence(0, blocks))
.expect("Failed to encode ECDSA private key as DER");

pem::encode(&pem::Pem {
tag: "PUBLIC KEY".to_string(),
contents: der_out,
})
}

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L92 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() {
return Ok(Quote::default());
}

// 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();
let _ = quote_file.read_to_end(&mut quote);
//// todo: wire up Qlerror and drop .expect()
let quote3 = Quote3::try_from(quote.as_ref()).expect("quote3 error");
let mrenclave = quote3.app_report_body().mr_enclave().to_string();
let mrsigner = quote3.app_report_body().mr_signer().to_string();

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 generate_ephemeral_keypair(notary_private: &str, notary_public: &str) {
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");

std::fs::write(notary_private, pem_string).expect("fs::write");

let der = signing_key
.verifying_key()
.to_encoded_point(true)
.to_bytes();
let pem_spki_pub = pem_der_encode_with_asn1(&der);
std::fs::write(notary_public, pem_spki_pub).expect("fs::write");
let _ = PUBLIC_KEY
.set(*signing_key.verifying_key())
.map_err(|_| "Public key has already been set");
}

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 182 in crates/notary/server/src/tee.rs

View check run for this annotation

Codecov / codecov/patch

crates/notary/server/src/tee.rs#L164-L182

Added lines #L164 - L182 were not covered by tests
}
}
}
}

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L186 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=dev
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"]
63 changes: 63 additions & 0 deletions crates/notary/server/tee/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# notary-server testing only
ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
ARCH_LIBDIR ?= /lib/$(shell $(CC) -dumpmachine)

SELF_EXE = ./notary-server

.PHONY: all
all: $(SELF_EXE) notary-server.manifest
ifeq ($(SGX),1)
all: notary-server.manifest.sgx notary-server.sig
endif

ifeq ($(DEBUG),1)
GRAMINE_LOG_LEVEL = debug
else
GRAMINE_LOG_LEVEL = error
endif

# Note that we're compiling in release mode regardless of the DEBUG setting passed
# to Make, as compiling in debug mode results in an order of magnitude's difference in
# performance that makes testing by running a benchmark with ab painful. The primary goal
# of the DEBUG setting is to control Gramine's loglevel.
-include $(SELF_EXE).d # See also: .cargo/config.toml
$(SELF_EXE): $(ROOT_DIR)../Cargo.toml
cargo build --bin notary-server --release --features tee_quote

notary-server.manifest: notary-server.manifest.template
cp ../../../../target/release/notary-server . && \
gramine-manifest \
-Dlog_level=$(GRAMINE_LOG_LEVEL) \
-Darch_libdir=$(ARCH_LIBDIR) \
-Dself_exe=$(SELF_EXE) \
$< $@

# Make on Ubuntu <= 20.04 doesn't support "Rules with Grouped Targets" (`&:`),
# see the helloworld example for details on this workaround.
notary-server.manifest.sgx notary-server.sig: sgx_sign
@:

.INTERMEDIATE: sgx_sign
sgx_sign: notary-server.manifest $(SELF_EXE)
gramine-sgx-sign \
--manifest $< \
--output $<.sgx

ifeq ($(SGX),)
GRAMINE = gramine-direct
else
GRAMINE = gramine-sgx
endif

.PHONY: start-gramine-server
start-gramine-server: all
$(GRAMINE) notary-server

.PHONY: clean
clean:
$(RM) -rf *.token *.sig *.manifest.sgx *.manifest result-* OUTPUT

.PHONY: distclean
distclean: clean
$(RM) -rf $(SELF_EXE) Cargo.lock

21 changes: 21 additions & 0 deletions crates/notary/server/tee/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#### gramine with intel SGX
```bash
SGX=1 make
```
```bash
SGX=1 make start-gramine-server
```
#### gramine emulating SGX
```
make
```
```
make start-gramine-server
```
#### generate measurement without SGX hardware
```
make
```
```
gramine-sgx-sigstruct-view --verbose --output-format=toml notary-server.sig
```
Loading