diff --git a/Makefile b/Makefile index bede1c512..28b454134 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ OPTEE_DIR_SDK ?= /work/rust-optee-trustzone-sdk/ AARCH64_OPENSSL_DIR ?= /work/rust-optee-trustzone-sdk/optee-qemuv8-3.7.0/build/openssl-1.0.2s/ AARCH64_GCC ?= $(OPTEE_DIR)/toolchains/aarch64/bin/aarch64-linux-gnu-gcc SGX_RUST_FLAG ?= "-L/work/sgxsdk/lib64 -L/work/sgxsdk/sdk_libs" +NITRO_RUST_FLAG ?= "" all: @echo $(WARNING_COLOR)"Please explicitly choose a target."$(RESET_COLOR) @@ -46,6 +47,12 @@ sgx: sdk sgx-env cd sonora-bind && RUSTFLAGS=$(SGX_RUST_FLAG) cargo build cd durango && RUSTFLAGS=$(SGX_RUST_FLAG) cargo build --lib --features sgx +nitro: sdk + pwd + RUSTFLAGS=$(NITRO_RUST_FLAG) $(MAKE) -C mexico-city nitro + RUSTFLAGS=$(NITRO_RUST_FLAG) $(MAKE) -C nitro-root-enclave + RUSTFLAGS=$(NITRO_RUST_FLAG) $(MAKE) -C nitro-root-enclave-server + # Compile for trustzone, note: source the rust-optee-trustzone-sdk/environment first, however assume `unset CC`. trustzone: sdk trustzone-env $(MAKE) -C mexico-city trustzone CC=$(AARCH64_GCC) @@ -98,6 +105,42 @@ trustzone-veracruz-test: trustzone test_cases trustzone-test-env trustzone-test-env: tz_test.sh run_tz_test.sh chmod u+x $^ +nitro-sinaloa-test: nitro test_cases + cd sinaloa-test \ + && RUSTFLAGS=$(NITRO_RUST_FLAG) cargo test --features nitro \ + && RUSTFLAGS=$(NITRO_RUST_FLAG) cargo test test_debug --features nitro,debug -- --ignored --test-threads=1 + cd sinaloa-test \ + && ./nitro_terminate.sh + cd ./sinaloa-test \ + && ./nitro_ec2_terminate_root.sh + +nitro-sinaloa-test-dry-run: nitro test_cases + cd sinaloa-test \ + && RUSTFLAGS=$(NITRO_RUST_FLAG) cargo test --features sgx --no-run + +nitro-sinaloa-performance: nitro test_cases + cd sinaloa-test \ + && RUSTFLAGS=$(NITRO_RUST_FLAG) cargo test test_performance_ --features nitro -- --ignored + cd sinaloa-test \ + && ./nitro_terminate.sh + cd ./sinaloa-test \ + && ./nitro_ec2_terminate_root.sh + +nitro-veracruz-test-dry-run: nitro test_cases + cd veracruz-test \ + && RUSTFLAGS=$(SGX_RUST_FLAG) cargo test --features nitro --no-run + +nitro-veracruz-test: nitro test_cases + cd veracruz-test \ + && RUSTFLAGS=$(SGX_RUST_FLAG) cargo test --features nitro + cd sinaloa-test \ + && ./nitro_terminate.sh + cd ./sinaloa-test \ + && ./nitro_ec2_terminate_root.sh + +nitro-psa-attestation: + cd psa-attestation && cargo build --features nitro + trustzone-env: unset CC rustup target add aarch64-unknown-linux-gnu arm-unknown-linux-gnueabihf @@ -116,12 +159,14 @@ clean: cd veracruz-utils && cargo clean cd sinaloa-test && cargo clean cd veracruz-test && cargo clean + cd nitro-root-enclave-server && cargo clean $(MAKE) clean -C mexico-city $(MAKE) clean -C jalisco $(MAKE) clean -C sinaloa $(MAKE) clean -C test-collateral $(MAKE) clean -C sonora $(MAKE) clean -C sdk + $(MAKE) clean -C nitro-root-enclave # NOTE: this target deletes ALL cargo.lock. clean-cargo-lock: diff --git a/NITRO_INSTRUCTIONS.markdown b/NITRO_INSTRUCTIONS.markdown new file mode 100644 index 000000000..72a079e8f --- /dev/null +++ b/NITRO_INSTRUCTIONS.markdown @@ -0,0 +1,119 @@ +# Setting up an environment for Veracruz on AWS Nitro Enclaves + +Start a standard EC2 instance, with M5.large, x86_64, with a security group that allows port 22 from all IPs, ports 3010 and 9090 from the VPC IPs, with an EBS volume attached (I use 64 GB for that). + +Log in to the instance. + +Mount the EBS volume (following these instructions: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-using-volumes.html): +```bash +lsblk +sudo file -s /dev/nvme1n1 +sudo mkfs -t xfs /dev/nvme1n1 +sudo mkdir /ebs_volume +sudo mount /dev/nvme1n1 /ebs_volume/ +``` + +Install some needed packages: +```bash +sudo yum install docker git +sudo yum install openssl11-devel +sudo yum install openssl11 +``` + +Install the AWS CLI tools (source: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html): +```bash +curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" +unzip awscliv2.zip +sudo ./aws/install +``` + +Following the directions here(https://www.crybit.com/change-default-data-image-directory-docker/), change the location where docker stores images (we do this because the default partition +is too small for the docker images, so we want them stored on the EBS volume): +```bash +sudo systemctl stop docker +sudo mv /var/lib/docker /ebs_volume +sudo ln -s /ebs_volume/docker /var/lib/docker +sudo systemctl start docker +``` + + +Now, get your docker image from wherever (these instructions show it being pulled from AWS ECR - https://docs.aws.amazon.com/AmazonECR/latest/userguide/getting-started-cli.html): +```bash +sudo usermod -a -G docker ec2-user +``` +logout +log back in + + +configure your AWS credentials: +```bash +aws configure +``` + +authenticate to your registry: +```bash +aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 768728991925.dkr.ecr.us-east-1.amazonaws.com +``` +pull the image: +```bash +docker pull +``` + + +Clone veracruz source: +```bash +git clone https://github.com/veracruz-project/veracruz.git --recursive +``` +Now, run the docker container: +```bash +export VERACRUZ_ROOT= +docker run -d -v $VERACRUZ_ROOT:/work/veracruz -v $HOME/.cargo/registry:/home//.cargo/registry --name veracruz +``` + +Now, start a shell in the container: +```bash +docker exec -u dermil01 -it veracruz bash +``` + +Install the AWS Nitro Enclaves CLI (source: https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave-cli-install.html) (TODO: This should be done by the docker build process): +```bash +sudo amazon-linux-extras install aws-nitro-enclaves-cli +sudo yum install aws-nitro-enclaves-cli-devel -y +sudo usermod -aG ne ec2-user +``` + +Configure nitro enclaves (source: From ) (TODO: Find out if this will work from inside a docker container). +Edit `/etc/nitro_enclaves/allocator.yaml` and set cpus = 2, memory to 256MB + +preparing the build environment inside the container (TODO: This should be done in the container build process): +install the target x86_64-unknown-linux-musl +```bash +rustup target add x86_64-unknown-linux-musl +``` + +Veracruz needs the ability to start another EC2 instance from your initial EC2 instance. The following instructions set up this ability. + +You need to get the subnet that your initial EC2 instance is on. + +The id of this subnet should be set in the environment varialbe AWS_SUBNET. + +You need to create a security group that allows ports 3010, 9090 for private IP addresses within the subnet. + +You probably also want to allow port 22 form all IPs to enable you to SSH into the instance (if you think you'll want to) + +The name of this security group should be set in the environment variable AWS_SECURITY_GROUP_ID + +You also need to set up an AWSK public/private key pair. You need the private key in a file on your initial EC2 instance. The path to this private key should be set in the environment variable AWS_PRIVATE_KEY_FILENAME. + +The name of this key pair (as known by AWS) should be set in the environment variable AWS_KEY_NAME. + +The AWS region that you are running on should be set in the environment variable AWS_REGION. + +To do this, it is recommended to set the variables in a file called nitro.env as follows: +```bash +export AWS_KEY_NAME="" +export AWS_PRIVATE_KEY_FILENAME="" +export AWS_SUBNET="" +export AWS_REGION="" +export AWS_SECURITY_GROUP_ID="" +``` diff --git a/baja/Cargo.toml b/baja/Cargo.toml index f43d908cf..915e33395 100644 --- a/baja/Cargo.toml +++ b/baja/Cargo.toml @@ -9,6 +9,7 @@ description = "TLS endpoint and session management for the trusted Veracruz runt sgx = ["veracruz-utils/sgx", "sgx_tstd", "sgx_types", "rustls/mesalock_sgx", "webpki/mesalock_sgx", "ring/mesalock_sgx", "webpki-roots/mesalock_sgx"] # NOTE: turn on the `std` on ring for Error trait tz = ["veracruz-utils/tz", "webpki/default", "webpki-roots/default", "ring/std", "ring/non_sgx", "optee-utee", "rustls/default"] +nitro = ["ring/std", "ring/non_sgx"] [dependencies] rustls = { git = "https://github.com/veracruz-project/rustls.git", branch = "veracruz" } diff --git a/baja/src/baja.rs b/baja/src/baja.rs index eb787409a..c0bda3729 100644 --- a/baja/src/baja.rs +++ b/baja/src/baja.rs @@ -345,26 +345,27 @@ impl Baja { ) }; - // NOTE: this should be randomly generated as below. But this is causing - // temporary problems on TrustZone, so instead of randomly generating - // it, we're using a fixed value for now. This **does not** compromise - // the security of the system in any way. - // - // let mut temp = vec![0; 3]; - // let rng = SystemRandom::new(); - // rng.fill(&mut temp).map_err(|_| "Error generating random bytes")?; - // It must be a valid DNS name, which must not be all numeric - // so we add an a at the beginning to be sure - // let full_string = - // format!("a{:02x}{:02x}{:02x}", temp[0], temp[1], temp[2]); - // full_string[..7].to_string() - - let name = FIXED_SERVER_NAME.to_string(); - + let name = { + // This should be randomly generated as below. But this is causing + // temporary problems on Trustzone, so instead of randomly generating + // it, we're using a static value for now. + // This does not compromise the security of the system + // TODO + //let mut temp = vec![0; 3]; + + //let rng = ring::rand::SystemRandom::new(); + //rng.fill(&mut temp) + //.map_err(|_| "Error generating random bytes")?; + // It must be a valid DNS name, which must not be all numeric + // so we add an a at the beginning to be sure + //let full_string = format!("a{:02x}{:02x}{:02x}", temp[0], temp[1], temp[2]); + //full_string[..7].to_string() + FIXED_SERVER_NAME.to_string() + }; let server_certificate_buffer = generate_certificate( - name.clone().into_bytes(), + name.as_bytes().to_vec(), server_private_key.clone(), - server_public_key.clone(), + server_public_key, &policy, )?; diff --git a/chihuahua/Cargo.toml b/chihuahua/Cargo.toml index 9726fdcf9..39509af79 100644 --- a/chihuahua/Cargo.toml +++ b/chihuahua/Cargo.toml @@ -11,6 +11,7 @@ default = [] std = ["wasmi/non_sgx", "platform-services/std", "wasmtime", "ring/non_sgx"] sgx = ["sgx_tstd", "wasmi/mesalock_sgx", "platform-services/sgx", "serde/mesalock_sgx", "typetag/mesalock_sgx", "ring/mesalock_sgx"] tz = ["platform-services/tz", "wasmi/non_sgx", "ring/non_sgx"] +nitro = ["platform-services/nitro"] [dependencies] byteorder = { git = "https://github.com/veracruz-project/byteorder.git", branch = "veracruz" } diff --git a/chihuahua/src/factory.rs b/chihuahua/src/factory.rs index 047e1f7c8..8d7ac8231 100644 --- a/chihuahua/src/factory.rs +++ b/chihuahua/src/factory.rs @@ -31,7 +31,7 @@ //! See the file `LICENSE.markdown` in the Veracruz root directory for licensing //! and copyright information. -#[cfg(any(feature = "std", feature = "tz"))] +#[cfg(any(feature = "std", feature = "tz", feature = "nitro"))] use std::sync::Mutex; #[cfg(feature = "sgx")] use std::sync::SgxMutex as Mutex; @@ -93,7 +93,7 @@ pub fn single_threaded_chihuahua( } } } - #[cfg(any(feature = "tz", feature = "sgx"))] + #[cfg(any(feature = "tz", feature = "sgx", feature = "nitro"))] { match strategy { ExecutionStrategy::Interpretation => { @@ -149,7 +149,7 @@ pub fn multi_threaded_chihuahua( } } } - #[cfg(any(feature = "tz", feature = "sgx"))] + #[cfg(any(feature = "tz", feature = "sgx", feature = "nitro"))] { match strategy { ExecutionStrategy::Interpretation => { diff --git a/durango/Cargo.toml b/durango/Cargo.toml index 9f534d388..21e0e2655 100644 --- a/durango/Cargo.toml +++ b/durango/Cargo.toml @@ -8,14 +8,16 @@ description = "Client code for provisioning secrets into, and otherwise interact [features] sgx = ["sgx_types", "sgx_ucrypto", "colima/sgx_attestation"] tz = [] +nitro = [] mock = ["mockall", "mockito"] [dependencies] # The cargo patch mechanism does NOT work when we add function into a macro_rules! -rustls = { git = "https://github.com/veracruz-project/rustls.git", branch = "self_signed" } -webpki = "0.21" -webpki-roots = "0.19" -ring = { version = "0.16", features = ["std"] } +rustls = { git = "https://github.com/veracruz-project/rustls.git", branch = "veracruz" } +webpki = { git = "https://github.com/veracruz-project/webpki.git", branch = "veracruz" } + +webpki-roots = { git = "https://github.com/veracruz-project/webpki-roots.git", branch = "veracruz"} +ring = { git = "https://github.com/veracruz-project/ring.git", branch = "veracruz"} reqwest = { version = "0.9", default-features=false } colima = { path = "../colima" } base64 = "0.10.1" diff --git a/durango/src/attestation.rs b/durango/src/attestation.rs index c6b1c118f..9f4723920 100644 --- a/durango/src/attestation.rs +++ b/durango/src/attestation.rs @@ -9,6 +9,8 @@ //! See the `LICENSE.markdown` file in the Veracruz root directory for //! information on licensing and copyright. +use veracruz_utils::EnclavePlatform; + #[cfg(feature = "mock")] use mockall::{automock, predicate::*}; @@ -16,6 +18,7 @@ use mockall::{automock, predicate::*}; pub trait Attestation { fn attestation( policy: &veracruz_utils::VeracruzPolicy, + target_platform: &EnclavePlatform, ) -> Result<(Vec, String), DurangoError>; } @@ -33,8 +36,14 @@ impl Attestation for AttestationPSA { /// Attestation against the global policy fn attestation( policy: &veracruz_utils::VeracruzPolicy, + target_platform: &EnclavePlatform, ) -> Result<(Vec, String), DurangoError> { - let expected_enclave_hash = hex::decode(policy.mexico_city_hash().as_str())?; + let mexico_city_hash = policy.mexico_city_hash(target_platform) + .map_err(|err| { + println!("Did not find mexico city hash for platform in policy:{:?}", err); + err + })?; + let expected_enclave_hash = hex::decode(mexico_city_hash.as_str())?; Self::attestation_flow( &policy.tabasco_url().as_str(), &policy.sinaloa_url().as_str(), diff --git a/durango/src/durango.rs b/durango/src/durango.rs index e79f3b916..76dd65972 100644 --- a/durango/src/durango.rs +++ b/durango/src/durango.rs @@ -16,7 +16,7 @@ use std::{ io::{Read, Write}, str::from_utf8, }; -use veracruz_utils::{VeracruzPolicy, VeracruzRole}; +use veracruz_utils::{EnclavePlatform, VeracruzPolicy, VeracruzRole}; use webpki; use webpki_roots; @@ -176,6 +176,7 @@ impl Durango { client_cert_filename: &str, client_key_filename: &str, policy_json: &str, + target_platform: &EnclavePlatform ) -> Result { let policy_hash = hex::encode(ring::digest::digest( &ring::digest::SHA256, @@ -186,10 +187,13 @@ impl Durango { let client_priv_key = Self::read_private_key(client_key_filename)?; // check if the certificate is valid - let key_pair = ring::signature::RsaKeyPair::from_der(client_priv_key.0.as_slice())?; + let key_pair = ring::signature::RsaKeyPair::from_der(client_priv_key.0.as_slice()) + .map_err(|err| { + DurangoError::RingError(format!("from_der failed:{:?}", err)) + })?; Self::check_certificate_validity(client_cert_filename, key_pair.public_key().as_ref())?; - let (enclave_cert_hash, enclave_name) = AttestationHandler::attestation(&policy)?; + let (enclave_cert_hash, enclave_name) = AttestationHandler::attestation(&policy, target_platform)?; let policy_ciphersuite_string = policy.ciphersuite().as_str(); diff --git a/durango/src/error.rs b/durango/src/error.rs index ca5243464..db6388f33 100644 --- a/durango/src/error.rs +++ b/durango/src/error.rs @@ -34,10 +34,8 @@ pub enum DurangoError { TLSUnspecifiedError, #[error(display = "Durango: TLSError: invalid cyphersuite {:?}.", _0)] TLSInvalidCyphersuiteError(std::string::String), - #[error(display = "Durango: RingUnspecifiedError: {:?}.", _0)] - RingUnspecifiedError(#[error(source)] ring::error::Unspecified), - #[error(display = "Durango: RingKeyRejectedError: {:?}.", _0)] - RingKeyRejectedError(#[error(source)] ring::error::KeyRejected), + #[error(display = "Durango: RingError: {:?}", _0)] + RingError(std::string::String), #[error(display = "Durango: SerdeJsonError: {:?}.", _0)] SerdeJsonError(#[error(source)] serde_json::error::Error), #[error(display = "Durango: X509Error: {:?}.", _0)] diff --git a/durango/src/tests.rs b/durango/src/tests.rs index 43d2beb63..daa0b8a8c 100644 --- a/durango/src/tests.rs +++ b/durango/src/tests.rs @@ -43,6 +43,7 @@ use actix_session::Session; use actix_web::http::StatusCode; use actix_web::{post, App, HttpRequest, HttpResponse, HttpServer}; use futures; +use veracruz_utils::EnclavePlatform; #[test] fn test_internal_read_all_bytes_in_file_succ() { @@ -91,7 +92,7 @@ fn test_internal_read_cert_invalid_private_key() { fn test_set_up_mock_object_for_attestation_handler() { // set up the attestation result as a mock object let handler = crate::attestation::MockAttestation::attestation_context(); - handler.expect().returning(|_| { + handler.expect().returning(|_, _| { Ok(( Durango::pub_read_cert(MOCK_ATTESTATION_ENCLAVE_CERT_FILENAME) .unwrap() @@ -103,7 +104,7 @@ fn test_set_up_mock_object_for_attestation_handler() { let policy: veracruz_utils::VeracruzPolicy = serde_json::from_str(policy_json.as_str()).unwrap(); - assert!(crate::attestation::MockAttestation::attestation(&policy).is_ok()); + assert!(crate::attestation::MockAttestation::attestation(&policy, &EnclavePlatform::Mock).is_ok()); } fn load_client_cert_and_private_key( @@ -132,7 +133,7 @@ fn load_client_cert_and_private_key( fn test_internal_init_self_signed_cert_client_config_succ() { // set up the attestation result as a mock object let handler = crate::attestation::MockAttestation::attestation_context(); - handler.expect().returning(|_| { + handler.expect().returning(|_, _| { Ok(( Durango::pub_read_cert(MOCK_ATTESTATION_ENCLAVE_CERT_FILENAME) .unwrap() @@ -148,8 +149,9 @@ fn test_internal_init_self_signed_cert_client_config_succ() { ) { Err(s) => panic!(s), Ok((cert, pkey, policy)) => { + let (enclave_cert, enclave_name) = - crate::attestation::MockAttestation::attestation(&policy).unwrap(); + crate::attestation::MockAttestation::attestation(&policy, &EnclavePlatform::Mock).unwrap(); let policy_ciphersuite_string = policy.ciphersuite().as_str(); assert!(Durango::pub_init_self_signed_cert_client_config( cert, @@ -167,7 +169,7 @@ fn test_internal_init_self_signed_cert_client_config_succ() { fn test_internal_init_self_signed_cert_client_config_invalid_ciphersuite() { // set up the attestation result as a mock object let handler = crate::attestation::MockAttestation::attestation_context(); - handler.expect().returning(|_| { + handler.expect().returning(|_, _| { Ok(( Durango::pub_read_cert(MOCK_ATTESTATION_ENCLAVE_CERT_FILENAME) .unwrap() @@ -184,7 +186,7 @@ fn test_internal_init_self_signed_cert_client_config_invalid_ciphersuite() { Err(s) => panic!(s), Ok((cert, pkey, policy)) => { let (enclave_cert, enclave_name) = - crate::attestation::MockAttestation::attestation(&policy).unwrap(); + crate::attestation::MockAttestation::attestation(&policy, &EnclavePlatform::Mock).unwrap(); let policy_ciphersuite_string = "WRONG CIPHERSUITE"; assert!(Durango::pub_init_self_signed_cert_client_config( cert, @@ -234,7 +236,7 @@ fn iterate_over_policy(path: &str, f: fn(Result) -> ()) { fn test_durango_new_succ() { // set up the attestation result as a mock object let handler = crate::attestation::MockAttestation::attestation_context(); - handler.expect().returning(|_| { + handler.expect().returning(|_, _| { Ok(( Durango::pub_read_cert(MOCK_ATTESTATION_ENCLAVE_CERT_FILENAME) .unwrap() @@ -244,9 +246,10 @@ fn test_durango_new_succ() { }); iterate_over_policy("../test-collateral/", |policy| { + assert!(policy.is_ok()); let policy = policy.unwrap(); - assert!(Durango::new(CLIENT_CERT_FILENAME, CLIENT_KEY_FILENAME, &policy).is_ok()); + assert!(Durango::new(CLIENT_CERT_FILENAME, CLIENT_KEY_FILENAME, &policy, &EnclavePlatform::Mock).is_ok()); }); } @@ -256,7 +259,7 @@ fn test_durango_new_succ() { fn test_durango_new_fail() { // set up the attestation result as a mock object let handler = crate::attestation::MockAttestation::attestation_context(); - handler.expect().returning(|_| { + handler.expect().returning(|_, _| { Ok(( Durango::pub_read_cert(MOCK_ATTESTATION_ENCLAVE_CERT_FILENAME) .unwrap() @@ -268,7 +271,7 @@ fn test_durango_new_fail() { iterate_over_policy("../test-collateral/invalid_policy/", |policy| { if let Ok(policy) = policy { assert!( - Durango::new(CLIENT_CERT_FILENAME, CLIENT_KEY_FILENAME, &policy).is_err(), + Durango::new(CLIENT_CERT_FILENAME, CLIENT_KEY_FILENAME, &policy, &EnclavePlatform::Mock).is_err(), format!("{:?}", policy) ); } @@ -279,7 +282,7 @@ fn test_durango_new_fail() { fn test_durango_new_unmatched_client_certificate() { // set up the attestation result as a mock object let handler = crate::attestation::MockAttestation::attestation_context(); - handler.expect().returning(|_| { + handler.expect().returning(|_, _| { Ok(( Durango::pub_read_cert(MOCK_ATTESTATION_ENCLAVE_CERT_FILENAME) .unwrap() @@ -289,7 +292,8 @@ fn test_durango_new_unmatched_client_certificate() { }); let policy_json = std::fs::read_to_string(POLICY_FILENAME).unwrap(); - let rst = Durango::new(DATA_CLIENT_CERT_FILENAME, CLIENT_KEY_FILENAME, &policy_json); + + let rst = Durango::new(DATA_CLIENT_CERT_FILENAME, CLIENT_KEY_FILENAME, &policy_json, &EnclavePlatform::Mock); assert!(rst.is_err()); } @@ -297,7 +301,7 @@ fn test_durango_new_unmatched_client_certificate() { fn test_durango_new_unmatched_client_key() { // set up the attestation result as a mock object let handler = crate::attestation::MockAttestation::attestation_context(); - handler.expect().returning(|_| { + handler.expect().returning(|_, _| { Ok(( Durango::pub_read_cert(MOCK_ATTESTATION_ENCLAVE_CERT_FILENAME) .unwrap() @@ -307,7 +311,8 @@ fn test_durango_new_unmatched_client_key() { }); let policy_json = std::fs::read_to_string(POLICY_FILENAME).unwrap(); - let rst = Durango::new(CLIENT_CERT_FILENAME, DATA_CLIENT_KEY_FILENAME, &policy_json); + + let rst = Durango::new(CLIENT_CERT_FILENAME, DATA_CLIENT_KEY_FILENAME, &policy_json, &EnclavePlatform::Mock); assert!(rst.is_err()); } @@ -315,7 +320,7 @@ fn test_durango_new_unmatched_client_key() { fn test_durango_new_invalid_enclave_name() { // set up the attestation result as a mock object with an invalid host url let handler = crate::attestation::MockAttestation::attestation_context(); - handler.expect().returning(|_| { + handler.expect().returning(|_, _| { Ok(( Durango::pub_read_cert(MOCK_ATTESTATION_ENCLAVE_CERT_FILENAME) .unwrap() @@ -325,7 +330,8 @@ fn test_durango_new_invalid_enclave_name() { }); let policy_json = std::fs::read_to_string(POLICY_FILENAME).unwrap(); - let rst = Durango::new(CLIENT_CERT_FILENAME, CLIENT_KEY_FILENAME, &policy_json); + + let rst = Durango::new(CLIENT_CERT_FILENAME, CLIENT_KEY_FILENAME, &policy_json, &EnclavePlatform::Mock); assert!(rst.is_err()); } @@ -338,7 +344,7 @@ async fn durango_policy_violations() { // tabasco server or communicate with IAS. This means that we are NOT // testing the attestation flow let handler = crate::attestation::MockAttestation::attestation_context(); - handler.expect().returning(|_| { + handler.expect().returning(|_policy, _target_platform| { Ok(( Durango::pub_read_cert(MOCK_ATTESTATION_ENCLAVE_CERT_FILENAME) .unwrap() @@ -389,10 +395,12 @@ async fn durango_policy_violations() { async fn policy_client_loop() -> Result<(), DurangoError> { let policy_json = std::fs::read_to_string(TRIPLE_POLICY_FILENAME).unwrap(); // TODO: Use a different policy file? + let mut data_client = Durango::new( DATA_CLIENT_CERT_FILENAME, DATA_CLIENT_KEY_FILENAME, &policy_json, + &EnclavePlatform::Mock, )?; let fake_data = vec![0xde, 0xad, 0xbe, 0xef]; @@ -407,6 +415,7 @@ async fn policy_client_loop() -> Result<(), DurangoError> { PROGRAM_CLIENT_CERT_FILENAME, PROGRAM_CLIENT_KEY_FILENAME, &policy_json, + &EnclavePlatform::Mock, )?; let sd_ret = program_client.send_data(&fake_data.to_vec()); @@ -459,7 +468,7 @@ fn durango_session() { let policy_json = std::fs::read_to_string(POLICY_FILENAME).unwrap(); let mut _durango = - crate::durango::Durango::new(CLIENT_CERT_FILENAME, CLIENT_KEY_FILENAME, &policy_json) + crate::durango::Durango::new(CLIENT_CERT_FILENAME, CLIENT_KEY_FILENAME, &policy_json, &EnclavePlatform::Mock) .unwrap(); let client_cert = { diff --git a/mexico-city/Cargo.toml b/mexico-city/Cargo.toml index f43e39ea2..9775436f5 100644 --- a/mexico-city/Cargo.toml +++ b/mexico-city/Cargo.toml @@ -18,6 +18,7 @@ path = "src/main.rs" [features] sgx = ["colima/sgx", "baja/sgx", "veracruz-utils/sgx", "wasmi/mesalock_sgx", "chihuahua/sgx", "rustls/mesalock_sgx", "protobuf/mesalock_sgx", "ring/mesalock_sgx", "sgx_types" , "sgx_tstd", "sgx_tcrypto", "sgx_tdh"] tz = ["wasmi/std", "baja/tz", "chihuahua/tz", "veracruz-utils/tz", "libc", "optee-utee-sys", "optee-utee", "serde_json/alloc"] +nitro = [ "chihuahua/nitro", "wasmi/non_sgx", "baja/nitro", "veracruz-utils/nitro", "nsm_io", "nsm_lib", "ring/nitro", "nix", "byteorder", "bincode" ] [dependencies] baja = { path = "../baja"} @@ -32,7 +33,12 @@ wasmi = { git = "https://github.com/veracruz-project/wasmi.git", branch = "verac hex = { version = "0.4.1", default-features = false } serde_json = { git = "https://github.com/veracruz-project/json.git", branch = "veracruz" } err-derive = "0.2" +nix = { version = "0.15", optional = true} +byteorder = { version = "1.3", optional = true } +bincode = { git = "https://github.com/veracruz-project/bincode.git", branch = "veracruz", default-features = false, optional = true } +nsm_lib = { git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git/", branch = "main", package="nsm-lib", optional = true } +nsm_io = { git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git/", branch = "main", package = "nsm-io", optional = true } [build-dependencies] uuid = { version = "0.7", features = ["v4"] } diff --git a/mexico-city/Dockerfile b/mexico-city/Dockerfile new file mode 100644 index 000000000..ae0d0af72 --- /dev/null +++ b/mexico-city/Dockerfile @@ -0,0 +1,6 @@ +# Note to self: Alpine Linux is the devil and should not be used +FROM alpine:latest +# copy the vsock-sample binary +COPY target/x86_64-unknown-linux-musl/release/mexico_city_enclave . +# start the server inside the enclave +CMD export RUST_BACKTRACE=1 && ./mexico_city_enclave diff --git a/mexico-city/Makefile b/mexico-city/Makefile index 8c518eb52..a4bd8b8b2 100644 --- a/mexico-city/Makefile +++ b/mexico-city/Makefile @@ -12,7 +12,7 @@ OUT_DIR?=. FINAL_DIR?=. -.PHONY: all sgx trustzone clean deprecated +.PHONY: all sgx trustzone clean deprecated nitro all: deprecated sgx @@ -144,6 +144,19 @@ $(OUT_DIR)/mexico_city_enclave: $(TZ_Src) @xargo build --target $(TZ_TARGET) --features tz --release --verbose --out-dir $(OUT_DIR) -Z unstable-options @echo $(INFO_COLOR)"GEN => mexico_city_enclave" $(RESET_COLOR) + +############# AWS Nitro Enclaves ################### +Nitro_Src = $(COMMON_Src) src/mc_nitro.rs src/main.rs + +nitro: mexico_city.eif + +mexico_city.eif: target/x86_64-unknown-linux-musl/release/mexico_city_enclave Dockerfile + nitro-cli build-enclave --docker-dir . --docker-uri mexico_city --output-file mexico_city.eif > measurements.json + cat measurements.json | jq -r '.Measurements.PCR0' > PCR0 + +target/x86_64-unknown-linux-musl/release/mexico_city_enclave: Cargo.toml $(Nitro_Src) + cargo build --target x86_64-unknown-linux-musl --release --features nitro + clean: @cargo clean @xargo clean diff --git a/mexico-city/src/main.rs b/mexico-city/src/main.rs index 3c11a1ff6..809180dc6 100644 --- a/mexico-city/src/main.rs +++ b/mexico-city/src/main.rs @@ -9,7 +9,7 @@ //! See the `LICENSE.markdown` file in the Veracruz root directory for //! information on licensing and copyright. -#![no_main] +#![cfg_attr(feature = "tz", no_main)] #![crate_name = "mexico_city_enclave"] #![feature(rustc_private)] @@ -18,3 +18,12 @@ pub mod mc_tz; #[cfg(feature = "tz")] pub use crate::mc_tz::*; pub mod managers; + +#[cfg(feature = "nitro")] +pub mod mc_nitro; + +#[cfg(feature = "nitro")] +fn main() -> Result<(), String> { + mc_nitro::nitro_main() + .map_err(|err| format!("Mexico City::main nitro_main returned error:{:?}", err)) +} diff --git a/mexico-city/src/managers/chihuahua_manager.rs b/mexico-city/src/managers/chihuahua_manager.rs index f2dce501f..364911215 100644 --- a/mexico-city/src/managers/chihuahua_manager.rs +++ b/mexico-city/src/managers/chihuahua_manager.rs @@ -19,7 +19,8 @@ use colima::colima::{ MexicoCityRequest as REQUEST, MexicoCityRequest_oneof_message_oneof as MESSAGE, }; use lazy_static::lazy_static; -#[cfg(feature = "tz")] +#[cfg(any(feature = "tz", feature = "nitro"))] + use std::sync::Mutex; #[cfg(feature = "sgx")] use std::sync::SgxMutex as Mutex; diff --git a/mexico-city/src/managers/error.rs b/mexico-city/src/managers/error.rs index 77bc08965..e7c48e187 100644 --- a/mexico-city/src/managers/error.rs +++ b/mexico-city/src/managers/error.rs @@ -10,10 +10,14 @@ //! information on licensing and copyright. use err_derive::Error; -#[cfg(feature = "tz")] +#[cfg(feature = "nitro")] +use nix; +#[cfg(any(feature = "tz", feature = "nitro"))] use std::sync::PoisonError; #[cfg(feature = "sgx")] use std::sync::PoisonError; +#[cfg(feature = "nitro")] +use veracruz_utils::nitro::{NitroRootEnclaveMessage, VeracruzSocketError}; #[derive(Debug, Error)] pub enum MexicoCityError { @@ -50,6 +54,24 @@ pub enum MexicoCityError { UninitializedProtocolState, #[error(display = "MexicoCity: Unavailable income buffer with ID {}.", _0)] UnavailableIncomeBufferError(u64), + #[cfg(feature = "nitro")] + #[error(display = "MexicoCity: Socket Error: {:?}", _0)] + SocketError(nix::Error), + #[cfg(feature = "nitro")] + #[error(display = "Mexico City: Veracruz Socket error:{:?}", _0)] + VeracruzSocketError(VeracruzSocketError), + #[cfg(feature = "nitro")] + #[error(display = "Mexico City: Bincode error:{:?}", _0)] + BincodeError(bincode::Error), + #[cfg(feature = "nitro")] + #[error(display = "Mexico City: NSM Lib error:{:?}", _0)] + NsmLibError(i32), + #[cfg(feature = "nitro")] + #[error(display = "Mexico City: NSM Error code:{:?}", _0)] + NsmErrorCode(nsm_io::ErrorCode), + #[cfg(feature = "nitro")] + #[error(display = "Mexico City: wrong message type received:{:?}", _0)] + WrongMessageTypeError(NitroRootEnclaveMessage), } impl From> for MexicoCityError { diff --git a/mexico-city/src/managers/mod.rs b/mexico-city/src/managers/mod.rs index 436f33aef..a9492deb7 100644 --- a/mexico-city/src/managers/mod.rs +++ b/mexico-city/src/managers/mod.rs @@ -13,7 +13,7 @@ use optee_utee::trace_println; #[cfg(feature = "sgx")] use sgx_types::sgx_status_t; -#[cfg(feature = "tz")] +#[cfg(any(feature = "tz", feature = "nitro"))] use std::sync::Mutex; use std::{ collections::HashMap, diff --git a/mexico-city/src/mc_nitro.rs b/mexico-city/src/mc_nitro.rs new file mode 100644 index 000000000..cb420e725 --- /dev/null +++ b/mexico-city/src/mc_nitro.rs @@ -0,0 +1,231 @@ +//! AWS Nitro-Enclaves-specific material for the Mexico City enclave +//! +//! ## Authors +//! +//! The Veracruz Development Team. +//! +//! ## Licensing and copyright notice +//! +//! See the `LICENSE.markdown` file in the Veracruz root directory for +//! information on licensing and copyright. + +use byteorder::{ByteOrder, LittleEndian}; +use nix::sys::socket::listen as listen_vsock; +use nix::sys::socket::{accept, bind, recv, send, MsgFlags, SockAddr}; +use nix::sys::socket::{socket, AddressFamily, SockFlag, SockType}; +use nsm_io; +use nsm_lib; +use std::convert::TryInto; +use std::os::unix::io::AsRawFd; +use std::os::unix::io::RawFd; +use veracruz_utils::{ + receive_buffer, send_buffer, vsocket, MCMessage, NitroRootEnclaveMessage, NitroStatus, +}; + +use crate::managers; +use crate::managers::MexicoCityError; + +/// The CID for the VSOCK to listen on +/// Currently set to all 1's so it will listen on all of them +const CID: u32 = 0xFFFFFFFF; // VMADDR_CID_ANY +/// The CID to send ocalls to that the non-secure host is listening on +const HOST_CID: u32 = 3; +/// The incoming port to listen on +const PORT: u32 = 5005; +/// max number of outstanding connectiosn in the socket listen queue +const BACKLOG: usize = 128; +/// The port to use when performing ocalls to the non-secure host +const OCALL_PORT: u32 = 5006; + +/// The maximum attestation document size +/// the value was copied from https://github.com/aws/aws-nitro-enclaves-sdk-c/blob/main/source/attestation.c +/// I've no idea where it came from (I've seen no documentation on this), but +/// I guess I have to trust Amazon on this one +const NSM_MAX_ATTESTATION_DOC_SIZE: usize = 16 * 1024; + +/// The main function for the Nitro mexico city enclave +pub fn nitro_main() -> Result<(), MexicoCityError> { + let socket_fd = socket( + AddressFamily::Vsock, + SockType::Stream, + SockFlag::empty(), + None, + ) + .map_err(|err| MexicoCityError::SocketError(err))?; + println!( + "mc_nitro::nitro_main creating SockAddr, CID:{:?}, PORT:{:?}", + CID, PORT + ); + let sockaddr = SockAddr::new_vsock(CID, PORT); + + bind(socket_fd, &sockaddr).map_err(|err| MexicoCityError::SocketError(err))?; + println!("mc_nitro::nitro_main calling accept"); + + listen_vsock(socket_fd, BACKLOG).map_err(|err| MexicoCityError::SocketError(err))?; + + let fd = accept(socket_fd).map_err(|err| MexicoCityError::SocketError(err))?; + println!("mc_nitro::nitro_main accept succeeded. looping"); + loop { + let received_buffer = + receive_buffer(fd).map_err(|err| MexicoCityError::VeracruzSocketError(err))?; + let received_message: MCMessage = bincode::deserialize(&received_buffer) + .map_err(|err| MexicoCityError::BincodeError(err))?; + let return_message = match received_message { + MCMessage::Initialize(policy_json) => initialize(&policy_json)?, + MCMessage::GetEnclaveCert => { + println!("mc_nitro::main GetEnclaveCert"); + let return_message = match managers::baja_manager::get_enclave_cert_pem() { + Ok(cert) => MCMessage::EnclaveCert(cert), + Err(_) => MCMessage::Status(NitroStatus::Fail), + }; + return_message + } + MCMessage::GetEnclaveName => { + println!("mc_nitro::main GetEnclaveName"); + let return_message = match managers::baja_manager::get_enclave_name() { + Ok(name) => MCMessage::EnclaveName(name), + Err(_) => MCMessage::Status(NitroStatus::Fail), + }; + return_message + } + MCMessage::NewTLSSession => { + println!("mc_nitro::main NewTLSSession"); + let ns_result = managers::baja_manager::new_session(); + let return_message: MCMessage = match ns_result { + Ok(session_id) => MCMessage::TLSSession(session_id), + Err(_) => MCMessage::Status(NitroStatus::Fail), + }; + return_message + } + MCMessage::CloseTLSSession(session_id) => { + println!("mc_nitro::main CloseTLSSession"); + let cs_result = managers::baja_manager::close_session(session_id); + let return_message: MCMessage = match cs_result { + Ok(_) => MCMessage::Status(NitroStatus::Success), + Err(_) => MCMessage::Status(NitroStatus::Fail), + }; + return_message + } + MCMessage::GetTLSDataNeeded(session_id) => { + println!("mc_nitro::main GetTLSDataNeeded"); + let return_message = match managers::baja_manager::get_data_needed(session_id) { + Ok(needed) => MCMessage::TLSDataNeeded(needed), + Err(_) => MCMessage::Status(NitroStatus::Fail), + }; + return_message + } + MCMessage::SendTLSData(session_id, tls_data) => { + println!("mc_nitro::main SendTLSData"); + let return_message = match managers::baja_manager::send_data(session_id, &tls_data) + { + Ok(_) => MCMessage::Status(NitroStatus::Success), + Err(_) => MCMessage::Status(NitroStatus::Fail), + }; + return_message + } + MCMessage::GetTLSData(session_id) => { + println!("mc_nitro::main GetTLSData"); + let return_message = match managers::baja_manager::get_data(session_id) { + Ok((active, output_data)) => MCMessage::TLSData(output_data, active), + Err(_) => MCMessage::Status(NitroStatus::Fail), + }; + return_message + } + MCMessage::GetPSAAttestationToken(challenge) => { + println!("mc_nitro::main GetPSAAttestationToken"); + get_psa_attestation_token(&challenge)? + } + MCMessage::ResetEnclave => { + // Do nothing here for now + println!("mc_nitro::main ResetEnclave"); + MCMessage::Status(NitroStatus::Success) + } + _ => { + println!("mc_nitro::main Unknown Opcode"); + MCMessage::Status(NitroStatus::Unimplemented) + } + }; + let return_buffer = bincode::serialize(&return_message) + .map_err(|err| MexicoCityError::BincodeError(err))?; + println!( + "mc_nitro::main calling send buffer with buffer_len:{:?}", + return_buffer.len() + ); + send_buffer(fd, &return_buffer).map_err(|err| MexicoCityError::VeracruzSocketError(err))?; + } +} + +/// Handler for the MCMessage::Initialize message +fn initialize(policy_json: &str) -> Result { + println!("mc_nitro::initialize started"); + managers::baja_manager::init_baja(policy_json)?; + println!("mc_nitro::main init_baja completed"); + return Ok(MCMessage::Status(NitroStatus::Success)); +} + +/// Handler for the MCMessage::GetPSAAttestationToken message +fn get_psa_attestation_token(challenge: &[u8]) -> Result { + println!("mc_nitro::get_psa_attestation_token started"); + println!( + "nc_nitro::get_psa_attestation_token received challenge:{:?}", + challenge + ); + + let enclave_cert = managers::baja_manager::get_enclave_cert_pem()?; + + let enclave_cert_hash = ring::digest::digest(&ring::digest::SHA256, &enclave_cert); + let nitro_token: Vec = { + let mut att_doc: Vec = vec![0; NSM_MAX_ATTESTATION_DOC_SIZE]; + let mut att_doc_len: u32 = att_doc.len() as u32; + + let nsm_fd = nsm_lib::nsm_lib_init(); + if nsm_fd < 0 { + return Err(MexicoCityError::NsmLibError(nsm_fd)); + } + let status = unsafe { + nsm_lib::nsm_get_attestation_doc( + nsm_fd, //fd + enclave_cert_hash.as_ref().as_ptr() as *const u8, // user_data + enclave_cert_hash.as_ref().len() as u32, // user_data_len + challenge.as_ptr(), // nonce_data + challenge.len() as u32, // nonce_len + std::ptr::null() as *const u8, // pub_key_data + 0 as u32, // pub_key_len + att_doc.as_mut_ptr(), // att_doc_data + &mut att_doc_len, // att_doc_len + ) + }; + match status { + nsm_io::ErrorCode::Success => (), + _ => return Err(MexicoCityError::NsmErrorCode(status)), + } + unsafe { + att_doc.set_len(att_doc_len as usize); + } + att_doc.clone() + }; + let enclave_name: String = managers::baja_manager::get_enclave_name()?; + let nre_message = + NitroRootEnclaveMessage::ProxyAttestation(challenge.to_vec(), nitro_token, enclave_name); + let nre_message_buffer = + bincode::serialize(&nre_message).map_err(|err| MexicoCityError::BincodeError(err))?; + + // send the buffer back to Sinaloa via an ocall + let vsocksocket = vsocket::vsock_connect(HOST_CID, OCALL_PORT) + .map_err(|err| MexicoCityError::SocketError(err))?; + send_buffer(vsocksocket.as_raw_fd(), &nre_message_buffer) + .map_err(|err| MexicoCityError::VeracruzSocketError(err))?; + let received_buffer = receive_buffer(vsocksocket.as_raw_fd()) + .map_err(|err| MexicoCityError::VeracruzSocketError(err))?; + let received_message: NitroRootEnclaveMessage = + bincode::deserialize(&received_buffer).map_err(|err| MexicoCityError::BincodeError(err))?; + + let (psa_token, pubkey, device_id) = match received_message { + NitroRootEnclaveMessage::PSAToken(token, pubkey, d_id) => (token, pubkey, d_id), + _ => return Err(MexicoCityError::WrongMessageTypeError(received_message)), + }; + let psa_token_message: MCMessage = + MCMessage::PSAAttestationToken(psa_token, pubkey, device_id.try_into().unwrap()); + + return Ok(psa_token_message); +} diff --git a/nitro-root-enclave-server/Cargo.toml b/nitro-root-enclave-server/Cargo.toml new file mode 100644 index 000000000..16d2862b6 --- /dev/null +++ b/nitro-root-enclave-server/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "nitro-root-enclave-server" +version = "0.1.0" +authors = ["The Veracruz Development Team"] +edition = "2018" +description = "The untrusted (non-secure world) socket server for the Veracruz root enclave for AWS Nitro enclaves" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +veracruz-utils = { path = "../veracruz-utils", features = ["nitro"]} +nix = "0.19" +hex = "0.4" +bincode = { git= "https://github.com/veracruz-project/bincode.git", branch= "veracruz", default-features=false } +err-derive = "0.2" +base64 = "0.10.1" +colima = { path = "../colima"} +stringreader = "0.1" +curl = "0.4" +clap = "2.33" +local_ipaddress = "0.1.3" + diff --git a/nitro-root-enclave-server/src/main.rs b/nitro-root-enclave-server/src/main.rs new file mode 100644 index 000000000..011406843 --- /dev/null +++ b/nitro-root-enclave-server/src/main.rs @@ -0,0 +1,349 @@ +//! Server for the Nitro Root Enclave +//! +//! ## Authors +//! +//! The Veracruz Development Team. +//! +//! ## Licensing and copyright notice +//! +//! See the `LICENSE.markdown` file in the Veracruz root directory for +//! information on licensing and copyright. + +use base64; +use bincode; +use clap::{App, Arg}; +use colima; +use curl::easy::{Easy, List}; +use err_derive::Error; +use hex; +use nix::sys::socket::{ + accept, bind, listen, socket, AddressFamily, InetAddr, IpAddr, SockAddr, SockFlag, SockType, +}; +use std::io::Read; +use stringreader; +use veracruz_utils::nitro_enclave::NitroError; +use veracruz_utils::{receive_buffer, send_buffer, NitroEnclave, NitroRootEnclaveMessage}; + +/// Maximum number of outstanding connections in the socket's +/// listen queue +const BACKLOG: usize = 128; + +/// The path to the enclave file that will be started +const NITRO_ROOT_ENCLAVE_EIF_PATH: &str = "/home/ec2-user/nitro_root_enclave.eif"; + +/// The inbound port number +const INBOUND_PORT: u16 = 9090; + +/// Nitro root enclave-specific errors +#[derive(Debug, Error)] +pub enum NitroServerError { + /// The root enclave returned an invalid message + #[error(display = "NitroServer: InvalidRootEnclaveMessage")] + InvalidRootEnclaveMessage, + /// A Bincode error was received + #[error(display = "NitroServer: Bincode Error:{:?}", _0)] + Bincode(#[error(source)] bincode::ErrorKind), + /// The enclave framework returned an error (this did not necessarily come + /// from the enclave itself + #[error(display = "NitroServer: Enclave error:{:?}", _0)] + EnclaveError(#[error(source)] NitroError), + /// An error was received from hex encoding or decoding + #[error(display = "NitroServer: Hex error:{:?}", _0)] + HexError(#[error(source)] hex::FromHexError), + /// A base64 decode error occurred + #[error(display = "NitroServer: Base64 Decode error:{:?}", _0)] + Base64Decode(#[error(source)] base64::DecodeError), + /// An invalid protocol buffer message was received + #[error(display = "NitroServer: Invalid Protocol Buffer Message")] + InvalidProtoBufMessage, + /// A remote http server returned a non-success (200) status + #[error(display = "NitroServer: Non-Success HTTP Response received")] + NonSuccessHttp, + /// Colima protocol buffer handling returned an error + #[error(display = "NitroServer: Colima error:{:?}", _0)] + Colima(colima::custom::ColimaError), + /// Curl returned an error + #[error(display = "NitroServer: Curl error:{:?}", _0)] + Curl(curl::Error), +} + +/// The main routine for the Nitro Root Enclave server +fn main() { + println!("Hello, world!"); + let matches = App::new("nitro-root-enclave-server") + .arg( + Arg::with_name("tabasco") + .takes_value(true) + .required(true) + .help("URL for Tabasco server"), + ) + .arg( + Arg::with_name("debug") + .long("debug") + .takes_value(false) + .help("Enables debug mode in the enclave"), + ) + .get_matches(); + let tabasco_url = matches.value_of("tabasco").unwrap(); // Since the tabasco argument is required, this should never actually panic + let enclave_debug = matches.is_present("debug"); + + // first, start the nitro-root-enclave + let enclave = loop { + match native_attestation(tabasco_url, enclave_debug) { + Err(err) => { + println!("nitro-root-enclave-server::main native_attestation failed({:?}). Sleeping and trying again.", err); + std::thread::sleep(std::time::Duration::from_secs(2)); + continue; + } + Ok(enc) => break enc, + } + }; + + let socket_fd = socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) + .expect("Failed to create socket_td"); + + let ip_string = local_ipaddress::get().expect("Failed to get local ip address"); + let ip_addr: Vec = ip_string + .split(".") + .map(|s| s.parse().expect("Parse error")) + .collect(); + let my_ip_address = InetAddr::new( + IpAddr::new_v4(ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]), + INBOUND_PORT, + ); + let sockaddr = SockAddr::new_inet(my_ip_address); + + bind(socket_fd, &sockaddr).expect("Failed to bind socket"); + + listen(socket_fd, BACKLOG).expect("Failed to listen to socket"); + + println!("nitro-root-enclave-server::main waiting for someone to connect on the socket"); + let fd = accept(socket_fd).expect("Failed to accept socket"); + loop { + println!("nitro-root-enclave-server::main reading buffer from other instance"); + let received_buffer = receive_buffer(fd).expect("Failed to receive buffer"); + println!("nitro-root-enclave-server::main forwarding buffer to enclave"); + enclave + .send_buffer(&received_buffer) + .expect("Failed to send buffer to enclave"); + println!("nitro-root-enclave-server::main reading buffer from enclave"); + let buffer_to_return = enclave + .receive_buffer() + .expect("Failed to receive buffer from enclave"); + println!("nitro-root-enclave-server::main forwarding return buffer to other instance"); + send_buffer(fd, &buffer_to_return).expect("Failed to return buffer to caller"); + } +} + +/// Start the nitro-root-enclave Nitro Enclave and handle it's native attestation +fn native_attestation( + tabasco_url: &str, + enclave_debug: bool, +) -> Result { + println!("nitro-root-enclave-server::native_attestation started"); + let nre_enclave = NitroEnclave::new(true, NITRO_ROOT_ENCLAVE_EIF_PATH, enclave_debug, None) + .map_err(|err| NitroServerError::EnclaveError(err))?; + + println!( + "nitro-root-enclave-server::native_attstation new completed. fetching firmware version" + ); + let firmware_version = fetch_firmware_version(&nre_enclave)?; + println!("nitro-root-enclave-server::native_attestation fetch_firmware_version complete. Now setting mexico city hash"); + + println!("SinaloaNitro::native_attestation completed setting Mexico City Hash. Now sending start to tabasco"); + let (challenge, device_id) = send_start(tabasco_url, "nitro", &firmware_version)?; + + println!("SinaloaNitro::native_attestation completed send to tabasco. Now sending NativeAttestation message to Nitro Root Enclave"); + let message = NitroRootEnclaveMessage::NativeAttestation(challenge, device_id); + let message_buffer = + bincode::serialize(&message).map_err(|err| NitroServerError::Bincode(*err))?; + nre_enclave.send_buffer(&message_buffer)?; + + // data returned is token, public key + let return_buffer = nre_enclave.receive_buffer()?; + let received_message = + bincode::deserialize(&return_buffer).map_err(|err| NitroServerError::Bincode(*err))?; + let (token, _public_key) = match received_message { + NitroRootEnclaveMessage::TokenData(tok, pubkey) => (tok, pubkey), + _ => return Err(NitroServerError::InvalidRootEnclaveMessage), + }; + + println!( + "nitro-root-enclave-server::native_attestation posting native_attestation_token to tabasco" + ); + post_native_attestation_token(tabasco_url, &token, device_id)?; + println!("nitro-root-enclave-server::native_attestation returning Ok"); + return Ok(nre_enclave); +} + +/// Send the native (AWS Nitro) attestation token to the Tabasco server +fn post_native_attestation_token( + tabasco_url: &str, + token: &Vec, + device_id: i32, +) -> Result<(), NitroServerError> { + println!("nitro-root-enclave-server::post_native_attestation_token started"); + let serialized_tabasco_request = + colima::serialize_native_psa_attestation_token(token, device_id) + .map_err(|err| NitroServerError::Colima(err))?; + let encoded_str = base64::encode(&serialized_tabasco_request); + let url = format!("{:}/Nitro/AttestationToken", tabasco_url); + println!( + "nitro-root-enclave-server::post_native_attestation_token posting to URL{:?}", + url + ); + let response = post_buffer(&url, &encoded_str)?; + + println!( + "nitro-root-enclave-server::post_psa_attestation_token received buffer:{:?}", + response + ); + return Ok(()); +} + +/// Fetch the firmware version from the nitro-root-enclave +fn fetch_firmware_version(nre_enclave: &NitroEnclave) -> Result { + println!("nitro-root-enclave-server::fetch_firmware_version started"); + + let firmware_version: String = { + let message = NitroRootEnclaveMessage::FetchFirmwareVersion; + let message_buffer = + bincode::serialize(&message).map_err(|err| NitroServerError::Bincode(*err))?; + println!( + "SinaloaNitro::Fetch_firmware_version sending message_buffer:{:?}", + message_buffer + ); + nre_enclave.send_buffer(&message_buffer)?; + + let returned_buffer = nre_enclave.receive_buffer()?; + let response: NitroRootEnclaveMessage = bincode::deserialize(&returned_buffer) + .map_err(|err| NitroServerError::Bincode(*err))?; + match response { + NitroRootEnclaveMessage::FirmwareVersion(version) => version, + _ => return Err(NitroServerError::InvalidRootEnclaveMessage), + } + }; + println!("nitro-root-enclave-server::fetch_firmware_version finished"); + return Ok(firmware_version); +} + +/// Send the start message to the Tabasco server (this triggers the server to +/// send the challenge) and then handle the response +fn send_start( + url_base: &str, + protocol: &str, + firmware_version: &str, +) -> Result<(Vec, i32), NitroServerError> { + let tabasco_response = send_tabasco_start(url_base, protocol, firmware_version)?; + if tabasco_response.has_psa_attestation_init() { + let (challenge, device_id) = + colima::parse_psa_attestation_init(tabasco_response.get_psa_attestation_init()) + .map_err(|err| NitroServerError::Colima(err))?; + return Ok((challenge, device_id)); + } else { + return Err(NitroServerError::InvalidProtoBufMessage); + } +} + +/// Post a buffer to a remote HTTP server +pub fn post_buffer(url: &str, buffer: &String) -> Result { + let mut buffer_reader = stringreader::StringReader::new(buffer); + + let mut curl_request = Easy::new(); + curl_request + .url(&url) + .map_err(|err| NitroServerError::Curl(err))?; + let mut headers = List::new(); + headers + .append("Content-Type: application/octet-stream") + .map_err(|err| NitroServerError::Curl(err))?; + curl_request + .http_headers(headers) + .map_err(|err| NitroServerError::Curl(err))?; + curl_request + .post(true) + .map_err(|err| NitroServerError::Curl(err))?; + curl_request + .post_field_size(buffer.len() as u64) + .map_err(|err| NitroServerError::Curl(err))?; + + let mut received_body = String::new(); + let mut received_header = String::new(); + { + let mut transfer = curl_request.transfer(); + + transfer + .read_function(|buf| Ok(buffer_reader.read(buf).unwrap_or(0))) + .map_err(|err| NitroServerError::Curl(err))?; + transfer + .write_function(|buf| { + received_body.push_str( + std::str::from_utf8(buf) + .expect(&format!("Error converting data {:?} from UTF-8", buf)), + ); + Ok(buf.len()) + }) + .map_err(|err| NitroServerError::Curl(err))?; + + transfer + .header_function(|buf| { + received_header.push_str( + std::str::from_utf8(buf) + .expect(&format!("Error converting data {:?} from UTF-8", buf)), + ); + true + }) + .map_err(|err| NitroServerError::Curl(err))?; + + transfer + .perform() + .map_err(|err| NitroServerError::Curl(err))?; + } + let header_lines: Vec<&str> = received_header.split("\n").collect(); + + println!( + "nitro-root-enclave-server::post_buffer received header:{:?}", + received_header + ); + if !received_header.contains("HTTP/1.1 200 OK\r") { + return Err(NitroServerError::NonSuccessHttp); + } + + println!( + "nitro-root-enclave-server::post_buffer header_lines:{:?}", + header_lines + ); + + return Ok(received_body); +} + +/// Send start to the tabasco server +pub fn send_tabasco_start( + url_base: &str, + protocol: &str, + firmware_version: &str, +) -> Result { + let serialized_start_msg = colima::serialize_start_msg(protocol, firmware_version) + .map_err(|err| NitroServerError::Colima(err))?; + let encoded_start_msg: String = base64::encode(&serialized_start_msg); + let url = format!("{:}/Start", url_base); + + println!( + "nitro-root-enclave-server::send_tabasco_start sending to url:{:?}", + url + ); + let received_body: String = post_buffer(&url, &encoded_start_msg)?; + println!("nitro-root-enclave-server::send_tabasco_start completed post command"); + + let body_vec = + base64::decode(&received_body).map_err(|err| NitroServerError::Base64Decode(err))?; + let response = + colima::parse_tabasco_response(&body_vec).map_err(|err| NitroServerError::Colima(err))?; + println!("nitro-root-enclave-server::send_tabasco_start completed. Returning."); + return Ok(response); +} diff --git a/nitro-root-enclave/Cargo.toml b/nitro-root-enclave/Cargo.toml new file mode 100644 index 000000000..c09bbb739 --- /dev/null +++ b/nitro-root-enclave/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "nitro-root-enclave" +version = "0.1.0" +authors = ["The Veracruz Development Team"] +edition = "2018" +description = "The Veracruz root enclave for AWS Nitro Enclaves" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nix = "0.15" +byteorder = { version = "1.3" } +veracruz-utils = { path = "../veracruz-utils", features = ["nitro"] } +bincode = { git= "https://github.com/veracruz-project/bincode.git", branch= "veracruz", default-features=false } +lazy_static = { version = "1.4.0", features = ["spin_no_std"]} +nsm_lib = { git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git/", branch = "main", package="nsm-lib" } +nsm_io = { git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git/", branch = "main", package = "nsm-io" } +ring = { git = "https://github.com/veracruz-project/ring.git", branch = "veracruz", features = ["nitro"]} +nitro-enclave-token = { git = "https://github.com/veracruz-project/nitro-enclave-token.git", branch = "main" } +psa-attestation = { path = "../psa-attestation/", features =["nitro"]} + diff --git a/nitro-root-enclave/Dockerfile b/nitro-root-enclave/Dockerfile new file mode 100644 index 000000000..8bd4b2399 --- /dev/null +++ b/nitro-root-enclave/Dockerfile @@ -0,0 +1,18 @@ +# docker image for the AWS Nitro Enclave for the Veracruz root enclave. +# +# AUTHORS +# +# The Veracruz Development Team. +# +# COPYRIGHT +# +# See the `LICENSE.markdown` file in the Veracruz root directory for licensing +# and copyright information. +# + +# Note to self: Alpine Linux is the devil and should not be used +FROM alpine:latest +# copy the vsock-sample binary +COPY target/x86_64-unknown-linux-musl/release/nitro-root-enclave . +# start the server inside the enclave +CMD export RUST_BACKTRACE=1 && ./nitro-root-enclave diff --git a/nitro-root-enclave/Makefile b/nitro-root-enclave/Makefile new file mode 100644 index 000000000..4460e0f44 --- /dev/null +++ b/nitro-root-enclave/Makefile @@ -0,0 +1,26 @@ +# This makefile is used to generate the AWS Nitro Enclave for the Veracruz root +# enclave +# +# AUTHORS +# +# The Veracruz Development Team. +# +# COPYRIGHT +# +# See the `LICENSE.markdown` file in the Veracruz root directory for licensing +# and copyright information. + +all: nitro_root_enclave.eif + +target/x86_64-unknown-linux-musl/release/nitro-root-enclave: Cargo.toml src/*.rs + cargo build --target x86_64-unknown-linux-musl --release + +nitro_root_enclave.eif: target/x86_64-unknown-linux-musl/release/nitro-root-enclave Dockerfile + nitro-cli build-enclave --docker-dir . --docker-uri nitro_root_enclave --output-file nitro_root_enclave.eif > measurements.json + cat measurements.json | jq -r '.Measurements.PCR0' > PCR0 + +.PHONY: +clean: + rm -f PCR0 + rm -f nitro_root_enclave.eif + cargo clean diff --git a/nitro-root-enclave/src/main.rs b/nitro-root-enclave/src/main.rs new file mode 100644 index 000000000..62fb8ecaf --- /dev/null +++ b/nitro-root-enclave/src/main.rs @@ -0,0 +1,484 @@ +//! AWS Nitro-Enclaves-specific material for the Root Enclave +//! +//! ## Authors +//! +//! The Veracruz Development Team. +//! +//! ## Licensing and copyright notice +//! +//! See the `LICENSE.markdown` file in the Veracruz root directory for +//! information on licensing and copyright. + +use lazy_static::lazy_static; +use nitro_enclave_token::{AttestationDocument, NitroToken}; +use nix::sys::socket::listen as listen_vsock; +use nix::sys::socket::{accept, bind, SockAddr}; +use nix::sys::socket::{socket, AddressFamily, SockFlag, SockType}; +use psa_attestation::{psa_initial_attest_get_token, psa_initial_attest_load_key, t_cose_sign1_get_verification_pubkey}; +use ring; +use ring::signature::KeyPair; +use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING}; +use std::sync::Mutex; +use veracruz_utils::{NitroRootEnclaveMessage, NitroStatus}; + +use veracruz_utils::{receive_buffer, send_buffer}; + +use nsm_io; +use nsm_lib; + +/// The CID to be listened to for messages from the non-secure world +const CID: u32 = 0xFFFFFFFF; /// VMADDR_CID_ANY +/// The Port to listen to for messages from the non-secure world +const PORT: u32 = 5005; +/// Maximum number of outstanding connections in the socket's +/// listen queue +const BACKLOG: usize = 128; + +/// The DER-encoded root certificate used to authenticate the certificate chain +/// (which is used to authenticate the Nitro Enclave tokens). +/// AWS claims that this certificate should never change (read: only change if +/// they have an extremely serious security issue), and it's expiry is set to +/// some time in 2049. +static AWS_NITRO_ROOT_CERTIFICATE: [u8; 533] = [ + 0x30, 0x82, 0x02, 0x11, 0x30, 0x82, 0x01, 0x96, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x11, 0x00, + 0xf9, 0x31, 0x75, 0x68, 0x1b, 0x90, 0xaf, 0xe1, 0x1d, 0x46, 0xcc, 0xb4, 0xe4, 0xe7, 0xf8, 0x56, + 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x30, 0x49, 0x31, 0x0b, + 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0f, 0x30, 0x0d, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x0c, 0x30, 0x0a, + 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x03, 0x41, 0x57, 0x53, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0c, 0x12, 0x61, 0x77, 0x73, 0x2e, 0x6e, 0x69, 0x74, 0x72, 0x6f, 0x2d, 0x65, + 0x6e, 0x63, 0x6c, 0x61, 0x76, 0x65, 0x73, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x30, 0x32, + 0x38, 0x31, 0x33, 0x32, 0x38, 0x30, 0x35, 0x5a, 0x17, 0x0d, 0x34, 0x39, 0x31, 0x30, 0x32, 0x38, + 0x31, 0x34, 0x32, 0x38, 0x30, 0x35, 0x5a, 0x30, 0x49, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, + 0x06, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0b, + 0x0c, 0x03, 0x41, 0x57, 0x53, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x12, + 0x61, 0x77, 0x73, 0x2e, 0x6e, 0x69, 0x74, 0x72, 0x6f, 0x2d, 0x65, 0x6e, 0x63, 0x6c, 0x61, 0x76, + 0x65, 0x73, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, + 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0xfc, 0x02, 0x54, 0xeb, 0xa6, 0x08, + 0xc1, 0xf3, 0x68, 0x70, 0xe2, 0x9a, 0xda, 0x90, 0xbe, 0x46, 0x38, 0x32, 0x92, 0x73, 0x6e, 0x89, + 0x4b, 0xff, 0xf6, 0x72, 0xd9, 0x89, 0x44, 0x4b, 0x50, 0x51, 0xe5, 0x34, 0xa4, 0xb1, 0xf6, 0xdb, + 0xe3, 0xc0, 0xbc, 0x58, 0x1a, 0x32, 0xb7, 0xb1, 0x76, 0x07, 0x0e, 0xde, 0x12, 0xd6, 0x9a, 0x3f, + 0xea, 0x21, 0x1b, 0x66, 0xe7, 0x52, 0xcf, 0x7d, 0xd1, 0xdd, 0x09, 0x5f, 0x6f, 0x13, 0x70, 0xf4, + 0x17, 0x08, 0x43, 0xd9, 0xdc, 0x10, 0x01, 0x21, 0xe4, 0xcf, 0x63, 0x01, 0x28, 0x09, 0x66, 0x44, + 0x87, 0xc9, 0x79, 0x62, 0x84, 0x30, 0x4d, 0xc5, 0x3f, 0xf4, 0xa3, 0x42, 0x30, 0x40, 0x30, 0x0f, + 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, + 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x90, 0x25, 0xb5, 0x0d, 0xd9, 0x05, + 0x47, 0xe7, 0x96, 0xc3, 0x96, 0xfa, 0x72, 0x9d, 0xcf, 0x99, 0xa9, 0xdf, 0x4b, 0x96, 0x30, 0x0e, + 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x0a, + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x03, 0x69, 0x00, 0x30, 0x66, 0x02, + 0x31, 0x00, 0xa3, 0x7f, 0x2f, 0x91, 0xa1, 0xc9, 0xbd, 0x5e, 0xe7, 0xb8, 0x62, 0x7c, 0x16, 0x98, + 0xd2, 0x55, 0x03, 0x8e, 0x1f, 0x03, 0x43, 0xf9, 0x5b, 0x63, 0xa9, 0x62, 0x8c, 0x3d, 0x39, 0x80, + 0x95, 0x45, 0xa1, 0x1e, 0xbc, 0xbf, 0x2e, 0x3b, 0x55, 0xd8, 0xae, 0xee, 0x71, 0xb4, 0xc3, 0xd6, + 0xad, 0xf3, 0x02, 0x31, 0x00, 0xa2, 0xf3, 0x9b, 0x16, 0x05, 0xb2, 0x70, 0x28, 0xa5, 0xdd, 0x4b, + 0xa0, 0x69, 0xb5, 0x01, 0x6e, 0x65, 0xb4, 0xfb, 0xde, 0x8f, 0xe0, 0x06, 0x1d, 0x6a, 0x53, 0x19, + 0x7f, 0x9c, 0xda, 0xf5, 0xd9, 0x43, 0xbc, 0x61, 0xfc, 0x2b, 0xeb, 0x03, 0xcb, 0x6f, 0xee, 0x8d, + 0x23, 0x02, 0xf3, 0xdf, 0xf6, +]; + +/// the following value was copied from https://github.com/aws/aws-nitro-enclaves-sdk-c/blob/main/source/attestation.c +/// I've no idea where it came from (I've seen no documentation on this), but +/// I guess I have to trust Amazon on this one +const NSM_MAX_ATTESTATION_DOC_SIZE: usize = 16 * 1024; + +lazy_static! { + /// The (randomly) self-generated device key pair + static ref DEVICE_KEY_PAIR: Mutex)>> = Mutex::new(None); + /// The hash value of the Mexico City enclave + static ref MEXICO_CITY_HASH: Mutex>> = Mutex::new(None); + /// The Device ID assigned to us by the Proxy Attestation Service + static ref DEVICE_ID: Mutex> = std::sync::Mutex::new(None); +} + +/// Query the enclave for its firmware version +fn get_firmware_version() -> Result { + println!("nitro-root-enclave::get_firmware_version"); + let version = env!("CARGO_PKG_VERSION"); + return Ok(version.to_string()); +} + +/// Set the hash value of mexico city to be included in the proxy attestation +/// token. +/// I DO NOT THINK THIS IS NECESSARY ANYMORE +fn set_mexico_city_hash_hack(hash: Vec) -> Result { + let mut mch_guard = MEXICO_CITY_HASH.lock().map_err(|err| { + format!( + "set_mexico_city_hash failed to obtain lock on MEXICO_CITY_HASH:{:?}", + err + ) + })?; + *mch_guard = Some(hash); + Ok(NitroStatus::Success) +} + +/// Perform the native attestation flow +fn native_attestation(challenge: &Vec, device_id: i32) -> Result<(Vec, Vec), String> { + let mut att_doc: Vec = vec![0; NSM_MAX_ATTESTATION_DOC_SIZE]; + + { + let mut di_guard = DEVICE_ID.lock().map_err(|err| { + format!( + "nitro-root-enclave::native_attestation failed to obtain lock on DEVICE_ID:{:?}", + err + ) + })?; + *di_guard = Some(device_id); + } + + let mut att_doc_len: u32 = att_doc.len() as u32; + let device_private_key = { + let dkp_guard = DEVICE_KEY_PAIR.lock().map_err(|err| format!("nitro-root-enclave::native_attestation failed to obtain lock on DEVICE_KEY_PAIR:{:?}", err))?; + match &*dkp_guard { + Some((_key, bytes)) => bytes.clone(), + None => return Err(format!("nitro-root-enclave::native_attestation for some reason the DEVICE_KEY_PAIR is uninitialized. I don't know how you got here")), + } + }; + let device_public_key = { + // Oddity: the current way of extracting the public key from the private + // key is to load it into PSA Crytpo, and then extract the public + // component. There are better ways to do this, but this is what + // I know to do now. It's ugly, but it works, but there are + // better ways + let mut device_key_handle: u16 = 0; + let status = unsafe { + psa_initial_attest_load_key( + device_private_key.as_ptr(), + device_private_key.len() as u64, + &mut device_key_handle, + ) + }; + if status != 0 { + println!("jalisco::create psa_initial_attest_load_key failed to load device private key with code:{:}", status); + return Err(format!("jalisco::create psa_initial_attest_load_key failed to load device private key with code:{:}", status)); + } + let mut public_key = std::vec::Vec::with_capacity(128); // TODO: Don't do this + let mut public_key_size: u64 = 0; + let status = unsafe { + t_cose_sign1_get_verification_pubkey( + device_key_handle, + public_key.as_mut_ptr() as *mut u8, + public_key.capacity() as u64, + &mut public_key_size as *mut u64, + ) + }; + if status != 0 { + println!( + "jalisco::create t_cose_sign1_get_verification_pubkey failed with error code:{:}", + status + ); + return Err(format!( + "jalisco::create t_cose_sign1_get_verification_pubkey failed with error code:{:}", + status + )); + } + unsafe { + public_key.set_len(public_key_size as usize); + } + public_key + }; + + let nsm_fd = nsm_lib::nsm_lib_init(); + if nsm_fd < 0 { + return Err(format!( + "nitro-root-enclave::native_attestation nsm_lib_init failed:{:?}", + nsm_fd + )); + } + let status = unsafe { + nsm_lib::nsm_get_attestation_doc( + nsm_fd, //fd + std::ptr::null(), // user_data + 0, // user_data_len + challenge.as_ptr(), // nonce_data + challenge.len() as u32, // nonce_len + device_public_key.as_ptr() as *const u8, // pub_key_data + device_public_key.len() as u32, // pub_key_len + att_doc.as_mut_ptr(), // att_doc_data + &mut att_doc_len, // att_doc_len + ) + }; + match status { + nsm_io::ErrorCode::Success => (), + _ => return Err(format!("nitro-root-enclave::native_attestation received non-success error code from nsm_lib:{:?}", status)), + } + unsafe { + att_doc.set_len(att_doc_len as usize); + } + println!( + "nitro-root-enclave::main::native_attestation returning token:{:?}", + att_doc + ); + return Ok((att_doc, device_public_key.to_vec())); +} + +/// Perform the proxy attestation service on behalf of a Mexico City enclave +/// running on another AWS Nitro Enclave +fn proxy_attestation( + challenge: &[u8], + native_token: &[u8], + enclave_name: String, +) -> Result, String> { + // first authenticate the native token + let document: AttestationDocument = + NitroToken::authenticate_token(native_token, &AWS_NITRO_ROOT_CERTIFICATE).map_err( + |err| { + format!( + "nitro-root-enclave::proxy_attestation failed to authenticate token:{:?}", + err + ) + }, + )?; + let enclave_cert_hash: Vec = match document.user_data { + Some(hash) => hash, + None => return Err(format!("nitro-root-enclave::proxy_attestation AttestationDocument does not contain user_data")), + }; + + // load the PSA key into PSA Crypto + let device_private_key = { + let dkp_guard = DEVICE_KEY_PAIR.lock() + .map_err(|err| format!("nitro-root-enclave:proxy_attestation failed to obtain lock on DEVICE_KEY_PAIR:{:?}", err))?; + match &*dkp_guard { + Some((_key, bytes)) => bytes.clone(), + None => { + return Err(format!( + "nitro-root-enclave::proxy_attestation Device Key pair is not populated?" + )) + } + } + }; + let mut device_key_handle: u16 = 0; + let status = unsafe { + psa_initial_attest_load_key( + device_private_key.as_ptr(), + device_private_key.len() as u64, + &mut device_key_handle, + ) + }; + if status != 0 { + return Err(format!("nitro-root-enclave:proxy_attestation psa_initial_attest_load_key failed with status code:{:?}", status)); + } + // generate the PSA Attestation token using challenge, measurement from the native token, and the enclave_cert_hash + let mut token: Vec = Vec::with_capacity(2048); // TODO: Don't do this + let mut token_len: u64 = 0; + let enclave_name_len: usize = enclave_name.len(); + // AWS Nitro PCRs are SHA384 hashes. The rest of our hashes are SHA256. + // We are truncating it in the PSA token so the offsets don't change between + // platforms + let pcr_len: u64 = if document.pcrs[0].len() > 32 { + 32 + } else { + return Err(format!( + "nitro-root-enclave:proxy_attestation document.pcrs[0] is too short. Wanted > 32, got:{:?}", document.pcrs[0].len() + )); + }; + let status = unsafe { + psa_initial_attest_get_token( + document.pcrs[0].as_ptr() as *const u8, + pcr_len as u64, + enclave_cert_hash.as_ptr() as *const u8, // user_data in the document is the certificate hash + enclave_cert_hash.len() as u64, + enclave_name.into_bytes().as_ptr() as *const i8, + enclave_name_len as u64, + challenge.as_ptr() as *const u8, + challenge.len() as u64, + token.as_mut_ptr() as *mut u8, + 2048, + &mut token_len as *mut u64, + ) + }; + if status != 0 { + return Err(format!("nitro-root-enclave::proxy_attestation psa_initial_attest_get_token failed with error code:{:?}", status)); + } + unsafe { token.set_len(token_len as usize) }; + + // return the proxy token + Ok(token.clone()) +} + +/// The main entry point for the Nitro Root enclave +fn main() -> Result<(), String> { + // generate the device private key + // Let's try it as an EC key, because RSA is like, old, man. + let rng = ring::rand::SystemRandom::new(); + println!("nitro-root-enclave::main generating key with rng. Which will probably hang, because why wouldn't it?"); + let pkcs8_bytes = EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &rng) + .map_err(|err| format!("Error generating PKCS-8:{:?}", err))? + .as_ref() + .to_vec(); + println!("nitro-root-enclave::main successfully generated key with rng. What the F do I know? I'm just a computer"); + let device_key_pair = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &pkcs8_bytes) + .map_err(|err| format!("nitro-root-enclave::main from_pkcs8 failed:{:?}", err))?; + { + let mut dkp_guard = DEVICE_KEY_PAIR.lock().map_err(|err| { + format!( + "nitro-root-enclave::main failed to obtain lock on DEVICE_KEY_PAIR:{:?}", + err + ) + })?; + *dkp_guard = Some((device_key_pair, pkcs8_bytes[38..70].to_vec())); + } + + println!( + "nitro-root-enclave::main successfully did the stupid plkcs8 to \"internal\" conversion." + ); + let socket_fd = socket( + AddressFamily::Vsock, + SockType::Stream, + SockFlag::empty(), + None, + ) + .map_err(|err| format!("nitro-root-enclave::main failed to create socket:{:?}", err))?; + + let sockaddr = SockAddr::new_vsock(CID, PORT); + + bind(socket_fd, &sockaddr) + .map_err(|err| format!("nitro-root-enclave::main bind failed:{:?}", err))?; + + listen_vsock(socket_fd, BACKLOG) + .map_err(|err| format!("nitro-root-enclave::main listen_vsock failed:{:?}", err))?; + + let fd = accept(socket_fd) + .map_err(|err| format!("nitro-root-enclave::main accept failed:{:?}", err))?; + loop { + let received_buffer = receive_buffer(fd) + .map_err(|err| format!("nitro-root-enclave::main receive_buffer failed:{:?}", err))?; + println!("nitro-root-enclave::main received_buffer.len:{:?}", received_buffer.len()); + let received_message: NitroRootEnclaveMessage = bincode::deserialize(&received_buffer).map_err(|err| format!("nitro-root-enclave::main failed to parse received buffer as NitroRootEnclaveMessage:{:?}", err))?; + let return_message = match received_message { + NitroRootEnclaveMessage::FetchFirmwareVersion => { + println!("nitro-root-enclave::main received FetchFirmwareVersion message"); + let version = get_firmware_version().map_err(|err| { + format!("nitro-root-enclave::main failed to get version:{:?}", err) + })?; + NitroRootEnclaveMessage::FirmwareVersion(version) + } + NitroRootEnclaveMessage::SetMexicoCityHashHack(hash) => { + let status = set_mexico_city_hash_hack(hash)?; + NitroRootEnclaveMessage::Status(status) + } + NitroRootEnclaveMessage::NativeAttestation(challenge, device_id) => { + println!("nitro-root-enclave::main received NativeAttestaion message"); + let (proxy_token, public_key) = + native_attestation(&challenge, device_id).map_err(|err| { + format!( + "nitro-root-enclave::main native_attestation failed:{:?}", + err + ) + })?; + NitroRootEnclaveMessage::TokenData(proxy_token, public_key) + } + NitroRootEnclaveMessage::ProxyAttestation(challenge, native_token, enclave_name) => { + println!("nitro-root-enclave::main received ProxyAttesstation message"); + let proxy_token = proxy_attestation(&challenge, &native_token, enclave_name) + .map_err(|err| { + println!("proxy_attestation failed"); + format!( + "nitro-root-enclave::main proxy_attestation failed:{:?}", + err + ) + })?; + let device_id: i32 = { + let di_guard = DEVICE_ID.lock().map_err(|err| { + println!("Failed to obtain lock on DEVICE_ID"); + format!( + "nitro-root-enclave::main failed to obtain lock on DEVICE_ID:{:?}", + err + ) + })?; + match &*di_guard { + Some(did) => *did, + None => { + return Err(format!( + "nitro-root-enclave::main DEVICE_ID is not set up?" + )) + } + } + }; + let device_private_key = { + let dkp_guard = DEVICE_KEY_PAIR.lock() + .map_err(|err| format!("ntiro-root-enclave:proxy_attestation failed to obtain lock on DEVICE_KEY_PAIR:{:?}", err))?; + match &*dkp_guard { + Some((_key, bytes)) => bytes.clone(), + None => { + return Err(format!( + "nitro-root-enclave::proxy_attestation Device Key pair is not populated?" + )) + } + } + }; + let device_public_key = { + // Oddity: the current way of extracting the public key from the private + // key is to load it into PSA Crytpo, and then extract the public + // component. There are better ways to do this, but this is what + // I know to do now. It's ugly, but it works, but there are + // better ways + let mut device_key_handle: u16 = 0; + println!("Starting with device_private_key value:{:?}", device_private_key); + let status = unsafe { + psa_initial_attest_load_key( + device_private_key.as_ptr(), + device_private_key.len() as u64, + &mut device_key_handle, + ) + }; + if status != 0 { + println!("jalisco::create psa_initial_attest_load_key failed to load device private key with code:{:}", status); + return Err(format!("nitro-root-enclave::proxy_attestation psa_initial_attest_load_key failed to load key with code:{:?}", status)); + } + println!("nitro-root-enclave::proxy_attestation device_key_handle:{:?}", device_key_handle); + let mut public_key = std::vec::Vec::with_capacity(1024); // TODO: Don't do this + let mut public_key_size: u64 = 0; + let status = unsafe { + t_cose_sign1_get_verification_pubkey( + device_key_handle, + public_key.as_mut_ptr() as *mut u8, + public_key.capacity() as u64, + &mut public_key_size as *mut u64, + ) + }; + if status != 0 { + println!( + "jalisco::create t_cose_sign1_get_verification_pubkey failed with error code:{:}", + status + ); + return Err(format!( + "jalisco::create t_cose_sign1_get_verification_pubkey failed with error code:{:}", + status + )); + } + println!("nitro-root-enclave::main public_key_size:{:?}", public_key_size); + unsafe { + public_key.set_len(public_key_size as usize); + } + println!("nitro-root-enclave::main returning public_key value:{:?}", public_key); + public_key.clone() + }; + println!("nitro-root-enclave::main finished handling proxy attestation message"); + NitroRootEnclaveMessage::PSAToken(proxy_token, device_public_key, device_id as u32) + } + _ => { + println!("nitro-root-enclave::main received unhandled message:{:?}", received_message); + return Err(format!( + "nitro-root-enclave::main received floopy unhandled message:{:?}", + received_message + )) + } + }; + let return_buffer = bincode::serialize(&return_message).map_err(|err| { + format!( + "nitro-root-enclave::main failed to serialize return_message:{:?}", + err + ) + })?; + println!( + "nitro-root-enclave::main returning return_buffer:{:?}", + return_buffer + ); + send_buffer(fd, &return_buffer).map_err(|err| { + format!( + "nitro-root-enclave::main failed to send return_buffer:{:?}", + err + ) + })?; + } +} diff --git a/platform-services/Cargo.toml b/platform-services/Cargo.toml index e6aaacf35..621162bf0 100644 --- a/platform-services/Cargo.toml +++ b/platform-services/Cargo.toml @@ -10,6 +10,7 @@ default = [] std = ["getrandom"] sgx = ["sgx_trts"] tz = ["optee-utee"] +nitro = ["nsm_io", "nsm_lib"] [lib] name = "platform_services" @@ -18,6 +19,8 @@ path = "./src/lib.rs" [dependencies] cfg-if = "0.1.10" getrandom = { version = "0.1.14", optional = true } +nsm_lib = { git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git/", branch = "main", package="nsm-lib", optional = true } +nsm_io = { git = "https://github.com/aws/aws-nitro-enclaves-nsm-api.git/", branch = "main", package = "nsm-io", optional = true } [target.'cfg(target_arch = "x86_64")'.dependencies] sgx_trts = { rev = "v1.1.2", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } diff --git a/platform-services/src/lib.rs b/platform-services/src/lib.rs index a5b50619f..3db80a006 100644 --- a/platform-services/src/lib.rs +++ b/platform-services/src/lib.rs @@ -33,12 +33,16 @@ cfg_if! { } else if #[cfg(feature = "tz")] { #[path="tz_platform_services.rs"] mod imp; + } else if #[cfg(feature = "nitro")] { + #[path="nitro_platform_services.rs"] + mod imp; } else if #[cfg(feature = "std")] { #[path="std_platform_services.rs"] mod imp; + } else { compile_error!( - "Unrecognised feature: platforms supported are SGX, TZ, and std."); + "Unrecognised feature: platforms supported are SGX, TZ, Nitro, and std."); } } diff --git a/platform-services/src/nitro_platform_services.rs b/platform-services/src/nitro_platform_services.rs new file mode 100644 index 000000000..525b07627 --- /dev/null +++ b/platform-services/src/nitro_platform_services.rs @@ -0,0 +1,37 @@ +//! AWS Nitro enclaves specific platform services +//! +//! Implements the `getrandom` service using a trusted entropy source provided +//! by the AWS Nitro Enclave environment. +//! +//! ## Authors +//! +//! The Veracruz Development Team. +//! +//! ## Licensing and copyright notice +//! +//! See the `LICENSE.markdown` file in the Veracruz root directory for +//! information on licensing and copyright. + +use super::result; +use nsm_io; +use nsm_lib; + +/// Fills a buffer, `buffer`, with random bytes sampled from the thread-local +/// random number source. Uses the AWS Nitro RNG +pub fn platform_getrandom(buffer: &mut [u8]) -> result::Result { + let nsm_fd = nsm_lib::nsm_lib_init(); + if nsm_fd < 0 { + return result::Result::UnknownError; + } + let mut buffer_len = buffer.len(); + + let status = unsafe { + nsm_lib::nsm_get_random(nsm_fd, buffer.as_mut_ptr(), &mut buffer_len) + }; + return match status { + nsm_io::ErrorCode::Success => result::Result::Success, + _ => result::Result::UnknownError, + }; +} + + diff --git a/psa-attestation/Cargo.toml b/psa-attestation/Cargo.toml index e9a492e60..34d252619 100644 --- a/psa-attestation/Cargo.toml +++ b/psa-attestation/Cargo.toml @@ -14,6 +14,7 @@ crate-type = ["rlib"] # build.rs depends on features tz = [] sgx = [] +nitro = [] [dependencies] libc = { git = "https://github.com/veracruz-project/libc.git", branch = "veracruz" } diff --git a/psa-attestation/build.rs b/psa-attestation/build.rs index 57118f43b..3f85e680c 100644 --- a/psa-attestation/build.rs +++ b/psa-attestation/build.rs @@ -21,6 +21,11 @@ fn main() { let cc = "/work/rust-optee-trustzone-sdk/optee/toolchains/aarch64/bin/aarch64-linux-gnu-gcc"; #[cfg(feature = "sgx")] let cc = "gcc"; + #[cfg(feature = "nitro")] + let cc = "musl-gcc"; + #[cfg(not(any(feature = "tz", feature = "sgx", feature = "nitro")))] + let cc = "gcc"; + let project_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let target_dir = env::var("OUT_DIR").unwrap(); diff --git a/sinaloa-test/Cargo.toml b/sinaloa-test/Cargo.toml index 53f48194e..8554b3634 100644 --- a/sinaloa-test/Cargo.toml +++ b/sinaloa-test/Cargo.toml @@ -10,14 +10,20 @@ description = "Veracruz integration test-suite. Tests trusted Veracruz runtime [features] sgx = ["sgx_ucrypto", "sgx_urts", "sgx_types", "sinaloa/sgx","veracruz-utils/std", "tabasco/sgx", "colima/sgx_attestation", "psa-attestation/sgx"] tz = ["sinaloa/tz", "veracruz-utils/tz", "tabasco/psa", "colima/tz", "psa-attestation/tz"] +nitro = ["sinaloa/nitro", "veracruz-utils/nitro", "tabasco/nitro"] +# debug feature means the enclaves will be started in debug mode (when available) +# which changes behaviors depending on the platform (for example in Debug mode, +# Nitro enclave attestation documents have the PCRs zeroed out and the console +# cannot connect) +debug = ["sinaloa/debug"] [dependencies] sinaloa = { path = "../sinaloa"} veracruz-utils = { path = "../veracruz-utils", features=["std"] } -webpki = "0.21" -webpki-roots = "0.19" -rustls = "0.16" -ring = "0.16" +webpki = { git = "https://github.com/veracruz-project/webpki.git", branch = "veracruz" } +webpki-roots = { git = "https://github.com/veracruz-project/webpki-roots.git", branch = "veracruz" } +rustls = { git = "https://github.com/veracruz-project/rustls.git", branch = "veracruz" } +ring = { git = "https://github.com/veracruz-project/ring.git", branch = "veracruz" } colima = { path = "../colima" } protobuf = "2.6" curl = "0.4.22" @@ -38,6 +44,8 @@ actix-rt = "1.0.0" env_logger = "0.7" log = "0.4" lazy_static = "1.4" +regex = "1.4" +local_ipaddress = "0.1.3" [target.'cfg(target_arch = "x86_64")'.dependencies] sgx_types = { rev = "v1.1.2", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } @@ -49,4 +57,6 @@ sgx_ucrypto = { branch="veracruz", git = 'https://github.com/veracruz-project/in sgx_types = { branch="veracruz", git = 'https://github.com/veracruz-project/incubator-teaclave-sgx-sdk.git', optional = true } [patch.crates-io] -rustls = { git = "https://github.com/veracruz-project/rustls.git", branch = "self_signed" } +rustls = { git = "https://github.com/veracruz-project/rustls.git", branch = "veracruz" } + + diff --git a/sinaloa-test/nitro_config.sh b/sinaloa-test/nitro_config.sh new file mode 100755 index 000000000..e37bcf1e9 --- /dev/null +++ b/sinaloa-test/nitro_config.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# AUTHORS +# +# The Veracruz Development Team. +# +# COPYRIGHT +# +# See the `LICENSE.markdown` file in the Veracruz root directory for licensing +# and copyright information. + +# Configures an EC2 instance to be able to run Nitro Enclaves for Veracruz + +nitro-cli-config -t 2 -m 512 diff --git a/sinaloa-test/nitro_console.sh b/sinaloa-test/nitro_console.sh new file mode 100755 index 000000000..6054f5689 --- /dev/null +++ b/sinaloa-test/nitro_console.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# AUTHORS +# +# The Veracruz Development Team. +# +# COPYRIGHT +# +# See the `LICENSE.markdown` file in the Veracruz root directory for licensing +# and copyright information. + +# Attaches to the console for any currently running Nitro enclave. Will throw +# an error if there are no currently enclaves + +ENCLAVE_ID=$(nitro-cli describe-enclaves | jq -r '.[0].EnclaveID') +echo $ENCLAVE_ID +nitro-cli console --enclave-id $ENCLAVE_ID diff --git a/sinaloa-test/nitro_ec2_terminate_root.sh b/sinaloa-test/nitro_ec2_terminate_root.sh new file mode 100755 index 000000000..6a0c12d03 --- /dev/null +++ b/sinaloa-test/nitro_ec2_terminate_root.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# AUTHORS +# +# The Veracruz Development Team. +# +# COPYRIGHT +# +# See the `LICENSE.markdown` file in the Veracruz root directory for licensing +# and copyright information. + +# Terminates any EC2 instances that were started by Sinaloa for the AWS Nitro +# enclaves feature. + +INFO=$(aws ec2 describe-instances --filters "Name=tag:Veracruz,Values=RootEnclave"| jq -r '.Reservations[].Instances[].InstanceId') +echo $INFO + +RESULT=$(aws ec2 terminate-instances --instance-ids $INFO) +echo $RESULT diff --git a/sinaloa-test/nitro_terminate.sh b/sinaloa-test/nitro_terminate.sh new file mode 100755 index 000000000..2ff10ae14 --- /dev/null +++ b/sinaloa-test/nitro_terminate.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# AUTHORS +# +# The Veracruz Development Team. +# +# COPYRIGHT +# +# See the `LICENSE.markdown` file in the Veracruz root directory for licensing +# and copyright information. + +# Terminates any currently running AWS nitro enclaves running on the current +# instance. + +INFO=$(nitro-cli describe-enclaves | jq -r '.[0].EnclaveID') +echo $INFO + +nitro-cli terminate-enclave --enclave-id $INFO diff --git a/sinaloa-test/populate_test_database.sh b/sinaloa-test/populate_test_database.sh old mode 100644 new mode 100755 index 22858526d..254c34aff --- a/sinaloa-test/populate_test_database.sh +++ b/sinaloa-test/populate_test_database.sh @@ -4,4 +4,7 @@ rm -f tabasco.db diesel --config-file ../tabasco/diesel.toml setup echo "INSERT INTO firmware_versions VALUES(1, 'sgx', '0.3.0', '${hash_value}');" > tmp.sql echo "INSERT INTO firmware_versions VALUES(2, 'psa', '0.3.0', 'deadbeefdeadbeefdeadbeefdeadbeeff00dcafef00dcafef00dcafef00dcafe');" >> tmp.sql +pcr0=`cat ../nitro-root-enclave/PCR0` +echo "INSERT INTO firmware_versions VALUES(3, 'nitro', '0.1.0', '${pcr0}');" >> tmp.sql sqlite3 tabasco.db < tmp.sql + diff --git a/sinaloa-test/src/main.rs b/sinaloa-test/src/main.rs index 58a7f06ad..111693346 100644 --- a/sinaloa-test/src/main.rs +++ b/sinaloa-test/src/main.rs @@ -29,6 +29,14 @@ mod tests { use sinaloa::SinaloaSGX as SinaloaEnclave; #[cfg(feature = "tz")] use sinaloa::SinaloaTZ as SinaloaEnclave; + #[cfg(feature = "nitro")] + use sinaloa::SinaloaNitro as SinaloaEnclave; + + use veracruz_utils::policy::EnclavePlatform; + + #[cfg(feature = "nitro")] + use regex::Regex; + use std::{ collections::HashMap, collections::HashSet, @@ -134,7 +142,10 @@ mod tests { std::env::set_var("RUST_LOG", "info,actix_server=debug,actix_web=debug"); let _main_loop_handle = std::thread::spawn(|| { let mut sys = System::new("Tabasco Server"); - let server = tabasco::server::server(tabasco_url).unwrap(); + #[cfg(feature="debug")] + let server = tabasco::server::server(tabasco_url, true).unwrap(); + #[cfg(not(feature="debug"))] + let server = tabasco::server::server(tabasco_url, false).unwrap(); sys.block_on(server).unwrap(); }); }); @@ -235,8 +246,16 @@ mod tests { let sinaloa = ret.unwrap(); + #[cfg(feature = "nitro")] + let test_target_platform: EnclavePlatform = EnclavePlatform::Nitro; + #[cfg(feature = "sgx")] + let test_target_platform: EnclavePlatform = EnclavePlatform::SGX; + #[cfg(feature = "tz")] + let test_target_platform: EnclavePlatform = EnclavePlatform::TrustZone; + + let mexico_city_hash = policy.mexico_city_hash(&test_target_platform).unwrap(); let enclave_cert_hash_ret = - attestation_flow(&policy.tabasco_url(), &policy.mexico_city_hash(), &sinaloa); + attestation_flow(&policy.tabasco_url(), &mexico_city_hash, &sinaloa); assert!(enclave_cert_hash_ret.is_ok()) } @@ -757,8 +776,16 @@ mod tests { Ok(id) } })?; + #[cfg(feature = "nitro")] + let test_target_platform: EnclavePlatform = EnclavePlatform::Nitro; + #[cfg(feature = "sgx")] + let test_target_platform: EnclavePlatform = EnclavePlatform::SGX; + #[cfg(feature = "tz")] + let test_target_platform: EnclavePlatform = EnclavePlatform::TrustZone; + + let mexico_city_hash = policy.mexico_city_hash(&test_target_platform).unwrap(); let enclave_cert_hash = if attestation_flag { - attestation_flow(&policy.tabasco_url(), &policy.mexico_city_hash(), &sinaloa)? + attestation_flow(&policy.tabasco_url(), &mexico_city_hash, &sinaloa)? } else { let enclave_cert = enclave_self_signed_cert(&sinaloa)?; ring::digest::digest(&ring::digest::SHA256, enclave_cert.as_ref()) @@ -1252,10 +1279,30 @@ mod tests { ) -> Result<(veracruz_utils::VeracruzPolicy, String, String), SinaloaError> { let policy_json = std::fs::read_to_string(fname).expect(&format!("Cannot open file {}", fname)); - let policy_hash = ring::digest::digest(&ring::digest::SHA256, policy_json.as_bytes()); - let policy_hash_str = hex::encode(&policy_hash.as_ref().to_vec()); - let policy = veracruz_utils::VeracruzPolicy::from_json(policy_json.as_str())?; - Ok((policy, policy_json, policy_hash_str)) + + // Since we need to run the root enclave on another system for nitro enclaves + // we can't use localhost for the URL of tabasco (like we do in the policy + // files. so we need to replace it with the private IP of the current instance + #[cfg(feature = "nitro")] + { + let ip_string = local_ipaddress::get() + .expect("Failed to get local ip address"); + let ip_address = format!("\"tabasco_url\": \"{:}:3010\"", ip_string); + let re = Regex::new(r#""tabasco_url": "\d+\.\d+.\d+.\d+:\d+""#).unwrap(); + let policy_json_cow = re.replace_all(&policy_json, ip_address.as_str()).to_owned(); + + let policy_hash = ring::digest::digest(&ring::digest::SHA256, policy_json_cow.as_ref().as_bytes()); + let policy_hash_str = hex::encode(&policy_hash.as_ref().to_vec()); + let policy = veracruz_utils::VeracruzPolicy::from_json(policy_json_cow.as_ref())?; + Ok((policy, policy_json_cow.as_ref().to_string(), policy_hash_str)) + } + #[cfg(not(feature = "nitro"))] + { + let policy_hash = ring::digest::digest(&ring::digest::SHA256, policy_json.as_bytes()); + let policy_hash_str = hex::encode(&policy_hash.as_ref().to_vec()); + let policy = veracruz_utils::VeracruzPolicy::from_json(policy_json.as_ref())?; + Ok((policy, policy_json.to_string(), policy_hash_str)) + } } /// Auxiliary function: initialise sinaloa from policy and open a tls session @@ -1553,7 +1600,7 @@ mod tests { } } Err(SinaloaError::DirectStrError( - "Terminate due to server crush", + "Terminate due to server crash", )) } @@ -1664,12 +1711,23 @@ mod tests { }); } let hash_bin = hex::decode(expected_enclave_hash)?; - if hash_bin != received_payload[47..79].to_vec() { - return Err(SinaloaError::MismatchError { - variable: "attestation_flow challenge", - received: received_payload[47..79].to_vec(), - expected: hash_bin.to_vec(), - }); + // specifying 0..32 because some platforms give us measurements in + // SHA384 (Nitro Enclaves, I'm talking about you) + // but the PSA attestation token only contains 32 bytes of it in order + // to keep the offsets the same + if hash_bin[0..32] != received_payload[47..79] { + #[cfg(all(feature = "debug", feature = "nitro"))] + { + println!("sinaloa-test::attestation_flow expected_enclave_hash did not match the value from the PSA token. However, since you are running Nitro enclaves in debug mode, their PCRs are zeroed. This is probably what's happened"); + } + #[cfg(not(feature = "debug"))] + { + return Err(SinaloaError::MismatchError { + variable: "attestation_flow hash_bin", + received: received_payload[47..79].to_vec(), + expected: hash_bin.to_vec(), + }); + } } let enclave_cert_hash = received_payload[86..118].to_vec(); Ok(enclave_cert_hash) diff --git a/sinaloa/Cargo.toml b/sinaloa/Cargo.toml index f3bb22995..994d777cf 100644 --- a/sinaloa/Cargo.toml +++ b/sinaloa/Cargo.toml @@ -8,6 +8,8 @@ description = "An untrusted server/bridge that allows the outside world and the [features] sgx = ["veracruz-utils/std","sgx_types", "sgx_urts", "colima/sgx_attestation", "mexico-city-bind", "sonora-bind"] tz = ["veracruz-utils/std", "veracruz-utils/tz", "colima/tz", "optee-teec", "uuid"] +nitro = ["veracruz-utils/nitro", "bincode", "serde/derive", "byteorder", "nix", "ssh2" ] +debug = [] [dependencies] dirs = "1.0.2" @@ -21,8 +23,6 @@ ring = "0.16" stringreader = "0.1" curl = "0.4" lazy_static = "1.4" -mexico-city-bind = { path = "../mexico-city-bind" } -sonora-bind = { path = "../sonora-bind" } actix-web = "2.0.0" actix-http = "1.0" futures = "0.3" @@ -30,8 +30,13 @@ log = "0.4" err-derive = "0.2" pinecone = "0.2" hex = "0.4" -webpki = "0.21.0" -rustls = "0.16.0" +webpki = { git = "https://github.com/veracruz-project/webpki.git", branch = "veracruz" } +rustls = { git = "https://github.com/veracruz-project/rustls.git", branch = "veracruz" } +bincode = { git = "https://github.com/veracruz-project/bincode.git", branch = "veracruz", default-features = false, optional = true } +serde = { git = "https://github.com/veracruz-project/serde.git", default-features = false, optional = true } +byteorder = { version = "1.3.2", optional = true } +nix = { version = "0.15", optional = true } +ssh2 = {version = "0.8.3", optional = true } [target.'cfg(target_arch = "x86_64")'.dependencies] sgx_types = { rev = "v1.1.2", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } diff --git a/sinaloa/build.rs b/sinaloa/build.rs index 192a054d1..95e9887c7 100644 --- a/sinaloa/build.rs +++ b/sinaloa/build.rs @@ -9,13 +9,17 @@ //! See the `LICENSE.markdown` file in the Veracruz root director for licensing //! and copyright information. +#[cfg(any(feature = "sgx", feature = "tz"))] use std::env; +#[cfg(feature = "tz")] use std::process::Command; +#[cfg(feature = "sgx")] use target_build_utils; fn main() { - let target = target_build_utils::TargetInfo::new().expect("could not get target info"); - if target.target_arch() == "x86_64" { + #[cfg(feature = "sgx")] + { + let target = target_build_utils::TargetInfo::new().expect("could not get target info"); let sdk_dir = env::var("SGX_SDK").unwrap_or_else(|_| "/work/sgxsdk".to_string()); let is_sim = env::var("SGX_MODE").unwrap_or_else(|_| "HW".to_string()); @@ -33,7 +37,9 @@ fn main() { } _ => println!("cargo:rustc-link-lib=dylib=sgx_urts"), // Treat undefined as HW } - } else { + } + #[cfg(feature = "tz")] + { let out_dir = env::var("OUT_DIR").unwrap(); let out_dir_arg = format!("OUT_DIR={:}", out_dir); diff --git a/sinaloa/src/ec2_instance.rs b/sinaloa/src/ec2_instance.rs new file mode 100644 index 000000000..b7ec8c38e --- /dev/null +++ b/sinaloa/src/ec2_instance.rs @@ -0,0 +1,395 @@ +//! Nitro-Enclave-specific material for Veracruz +//! +//! ## Authors +//! +//! The Veracruz Development Team. +//! +//! ## Licensing and copyright notice +//! +//! See the `LICENSE.markdown` file in the Veracruz root directory for +//! information on licensing and copyright. + +use std::env; +use std::fs::File; +use std::io::Read; +use std::io::Write; +use std::net::TcpStream; +use std::os::unix::io::RawFd; +use std::path::Path; +use std::process::Command; + +use err_derive::Error; +use serde_json::Value; +use ssh2::Session; + +use nix::sys::socket::{ + connect, shutdown, socket, AddressFamily, InetAddr, IpAddr, Shutdown, SockAddr, SockFlag, + SockType, +}; +use veracruz_utils; + +/// A struct containing specifics about an EC2 instance +pub struct EC2Instance { + pub instance_id: String, + pub private_ip: String, + pub socket_port: u16, + pub socket_fd: Option, +} + +/// Errors that may occur in the handling of EC2 instances +#[derive(Debug, Error)] +pub enum EC2Error { + /// A std::io::Error occurred + #[error(display = "EC2: IO Error:{:?}", _0)] + IOError(std::io::Error), + /// An error handling UTF8 strings occurred + #[error(display = "EC2: UTF8 Error:{:?}", _0)] + Utf8Error(std::str::Utf8Error), + /// Serde JSON returned an error + #[error(display = "EC2: Serde JSON Error:{:?}", _0)] + SerdeJsonError(serde_json::Error), + /// Incorrect JSON was encountered + #[error(display = "EC2: Incorrect JSON")] + IncorrectJson, + /// An SSH error occurred + #[error(display = "EC2: SSH2 Error:{:?}", _0)] + SSH2Error(ssh2::Error), + /// The contacted server (an EC2 instance we started) did not present a + /// host key when it was contacted + #[error(display = "EC2: No Host Key")] + NoHostKeyError, + /// A Veracruz-specific socket error was enountered + #[error(display = "EC2: Veracruz Socket Error:{:?}", _0)] + VeracruzSocketError(#[error(source)] veracruz_utils::VeracruzSocketError), + /// A call to a function was "successful" but it returned a non-zero status + /// value. This typically happens with calls to libraries following the + /// Unix calling conventions. + #[error(display = "EC2: Command non-zero status error:{:?}", _0)] + CommandNonZeroStatus(i32), + /// an error was returned by the AWS CLI + #[error(display = "EC2: CLI error")] + CLIError, +} + +impl EC2Instance { + /// instantiate an EC2 instance, which involves calling the AWS CLI to start + /// the instance, and then collecting information about that instance + /// Note that standard practice on AWS CLI calls is to return immediatly, + /// even before the instance has completed booting. In order to alleviate + /// that problem, this function sleeps for a set amount of time to give the + /// instance time to boot before returning, so the caller of this function + /// can be confident that they can contact the instance + pub fn new() -> Result { + let aws_key_name = + env::var("AWS_KEY_NAME").expect("Failed to read AWS_KEY_NAME environment variable."); + let aws_subnet = + env::var("AWS_SUBNET").expect("Failed to read AWS_SUBNET environment variable."); + let aws_region = + env::var("AWS_REGION").expect("Failed to read AWS_REGION environment variable."); + let aws_security_group_id = env::var("AWS_SECURITY_GROUP_ID") + .expect("Failed to read AWS_SECURITY_GROUP_ID environment variable."); + + let subnet_option = format!("--subnet-id={:}", aws_subnet); + + let ec2_result = Command::new("/usr/local/bin/aws") + .args(&[ + "ec2", + "run-instances", + "--image-id", + "ami-037dd1d3f98da4d50", + "--instance-type", + "m5.xlarge", + "--enclave-options", + "Enabled=true", + "--region", + &aws_region, + "--key-name", + &aws_key_name, + &subnet_option, + "--security-group-ids", + &aws_security_group_id, + "--associate-public-ip-address", + "--tag-specifications", + "ResourceType=instance,Tags=[{Key=Veracruz,Value=RootEnclave}]", + ]) + .output() + .map_err(|err| { + println!("EC2Instance::new failed to start ec2 instance:{:?}", err); + EC2Error::IOError(err) + })?; + if !ec2_result.status.success() { + let ec2_result_text = + std::str::from_utf8(&ec2_result.stderr).map_err(|err| EC2Error::Utf8Error(err))?; + println!("ec2 result Stdout:{:}", ec2_result_text); + return Err(EC2Error::CLIError); + } + + let ec2_result_text = + std::str::from_utf8(&ec2_result.stdout).map_err(|err| EC2Error::Utf8Error(err))?; + let ec2_data: Value = + serde_json::from_str(ec2_result_text).map_err(|err| EC2Error::SerdeJsonError(err))?; + + let instance_id: &str = match &ec2_data["Instances"][0]["InstanceId"] { + Value::String(value) => value, + _ => return Err(EC2Error::IncorrectJson), + }; + let private_ip: &str = match &ec2_data["Instances"][0]["PrivateIpAddress"] { + Value::String(value) => value, + _ => return Err(EC2Error::IncorrectJson), + }; + println!("EC2Instance instance_id:{:?}", instance_id); + println!("EC2Instance private_ip:{:?}", private_ip); + + // When you start an EC2 instance, the CLI returns to you immediately, + // with the details of the instance. However, that instance has not yet + // completed booting. The networking is not up, yet, and the OS on the + // instance hasn't completed it's boot sequence. I have found that 30s + // is a sufficient amount of time to let this happen (in most cases). + // Could it be less? In most cases, probably yes, but AWS gives you no + // promises about the amount of time this will take (as they really + // can't, because they can't really make promises about how long an + // arbitrary OS takes to boot). + std::thread::sleep(std::time::Duration::from_millis(30000)); + + let socket_port: u16 = 9090; + + Ok(EC2Instance { + instance_id: instance_id.to_string(), + private_ip: private_ip.to_string(), + socket_port: socket_port, + socket_fd: None, + }) + } + + /// Connect to the `socket_fd` socker on the EC2 instance + fn socket_connect(&mut self) -> Result { + let sockaddr = self.get_private_sockaddr()?; + + let socket_fd = { + loop { + match socket( + AddressFamily::Inet, + SockType::Stream, + SockFlag::empty(), + None, + ) { + Ok(fd) => break fd, + Err(nix::Error::Sys(err)) => match err { + nix::errno::Errno::ECONNREFUSED => { + println!("EC2Instance::socket failed, ECONNREFUSED, trying again"); + continue; + } + _ => panic!(format!("Failed to create socket:{:?}", err)), + }, + Err(err) => panic!(format!("Failed to create socket:{:?}", err)), + } + } + }; + while let Err(_err) = connect(socket_fd, &sockaddr) {} + + self.socket_fd = Some(socket_fd); + + return Ok(socket_fd); + } + + /// Get the private IP address of the EC2 instance, and return it as a + /// SockAddr + fn get_private_sockaddr(&self) -> Result { + let ip_addr: Vec = self + .private_ip + .split(".") + .map(|s| s.parse().expect("Parse error")) + .collect(); + let inet_addr: InetAddr = InetAddr::new( + IpAddr::new_v4(ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]), + self.socket_port, + ); + return Ok(SockAddr::new_inet(inet_addr)); + } + + /// Close the `socket_fd` socket to the EC2 instance, and then shutdown the + /// instance (using the AWS CLI) + pub fn close(&mut self) -> Result<(), EC2Error> { + if let Some(socket_fd) = self.socket_fd.take() { + match shutdown(socket_fd, Shutdown::Both) { + Ok(_) => (), // shutdown was successful, continue on your merry way + Err(err) => { + // shutdown was not successful, still attmept to finish the close operation + println!("EC2Instance::close failed to shutdown socket({:?}). We're gonna keep going, though", err); + } + } + } + println!("EC2InstanFce::close attempting to shutdown instance"); + let _ec2_result = Command::new("/usr/local/bin/aws") + .args(&[ + "ec2", + "terminate-instances", + "--instance-ids", + &self.instance_id, + ]) + .output() + .map_err(|err| EC2Error::IOError(err))?; + + println!("EC2Instance::close completed"); + return Ok(()); + } + + /// Execute a specified command on the EC2 instance. + /// This is performed by creating an SSH tunnel to the instance. + pub fn execute_command(&self, command: &str) -> Result<(), EC2Error> { + let full_ip = format!("{:}:{:}", self.private_ip, 22); + let tcp = TcpStream::connect(full_ip.clone()).map_err(|err| EC2Error::IOError(err))?; + + let mut session: Session = Session::new().unwrap(); + session.set_tcp_stream(tcp); + session + .handshake() + .map_err(|err| EC2Error::SSH2Error(err))?; + + let (key, key_type) = match session.host_key() { + Some((k, kt)) => (k, kt), + None => return Err(EC2Error::NoHostKeyError), + }; + + let mut known_hosts = session + .known_hosts() + .map_err(|err| EC2Error::SSH2Error(err))?; + known_hosts + .add(&full_ip, key, &full_ip, key_type.into()) + .map_err(|err| EC2Error::SSH2Error(err))?; + + let aws_private_key_filename = env::var("AWS_PRIVATE_KEY_FILENAME") + .expect("Failed to read AWS_PRIVATE_KEY_FILENAME environment variable."); + let privkey_path: &Path = Path::new(&aws_private_key_filename); + session + .userauth_pubkey_file("ec2-user", None, privkey_path, None) + .map_err(|err| EC2Error::SSH2Error(err))?; + let mut channel = session + .channel_session() + .map_err(|err| EC2Error::SSH2Error(err))?; + channel + .exec(command) + .map_err(|err| EC2Error::SSH2Error(err))?; + let mut s = String::new(); + channel + .read_to_string(&mut s) + .map_err(|err| EC2Error::IOError(err))?; + channel + .wait_close() + .map_err(|err| EC2Error::SSH2Error(err))?; + let exit_status = channel + .exit_status() + .map_err(|err| EC2Error::SSH2Error(err))?; + if exit_status != 0 { + println!( + "EC2Instance::excute_command SSH2 Session returned with non-zero exit-status:{:?}", + exit_status + ); + return Err(EC2Error::CommandNonZeroStatus(exit_status)); + } + Ok(()) + } + + /// Upload a file to the EC2 instance. This is done via scp_send + pub fn upload_file(&self, filename: &str, dest: &str) -> Result<(), EC2Error> { + let file_data: Vec = self.read_file(filename).map_err(|err| { + println!( + "EC2Instance::upload_file failed to read file:{:?}, received error:{:?}", + filename, err + ); + err + })?; + let full_ip = format!("{:}:{:}", self.private_ip, 22); + let tcp = TcpStream::connect(full_ip.clone()).map_err(|err| { + println!( + "EC2Instance::upload_file Failed to connect to EC2instance:{:?}", + err + ); + EC2Error::IOError(err) + })?; + + let mut session: Session = Session::new().unwrap(); + session.set_tcp_stream(tcp); + session.handshake().map_err(|err| { + println!("EC2Instance::upload_file failed handshake:{:?}", err); + EC2Error::SSH2Error(err) + })?; + + let (key, key_type) = match session.host_key() { + Some((k, kt)) => (k, kt), + None => return Err(EC2Error::NoHostKeyError), + }; + + let mut known_hosts = session + .known_hosts() + .map_err(|err| EC2Error::SSH2Error(err))?; + known_hosts + .add(&full_ip, key, &full_ip, key_type.into()) + .map_err(|err| EC2Error::SSH2Error(err))?; + + let aws_private_key_filename = env::var("AWS_PRIVATE_KEY_FILENAME") + .expect("Failed to read AWS_PRIVATE_KEY_FILENAME environment variable."); + let privkey_path: &Path = Path::new(&aws_private_key_filename); + session + .userauth_pubkey_file("ec2-user", None, privkey_path, None) + .map_err(|err| EC2Error::SSH2Error(err))?; + + let mut remote_file = session + .scp_send(Path::new(dest), 0o777, file_data.len() as u64, None) + .map_err(|err| EC2Error::SSH2Error(err))?; + let _num_written = remote_file.write_all(&file_data).map_err(|err| { + println!( + "EC2Instance::upload_file failed to write file data:{:?}", + err + ); + EC2Error::IOError(err) + })?; + Ok(()) + } + + /// Read the filename into a Vec (so it's binary) + fn read_file(&self, filename: &str) -> Result, EC2Error> { + let path = Path::new(filename); + + let mut file = File::open(&path).map_err(|err| EC2Error::IOError(err))?; + let mut buffer: Vec = Vec::new(); + file.read_to_end(&mut buffer) + .map_err(|err| EC2Error::IOError(err))?; + + Ok(buffer) + } + + /// sned a buffer to the EC2 instance via sockets, using `socket_fd` + pub fn send_buffer(&mut self, buffer: &Vec) -> Result<(), EC2Error> { + let socket_fd = match self.socket_fd { + Some(socket_fd) => socket_fd, + None => self.socket_connect()?, + }; + veracruz_utils::send_buffer(socket_fd, buffer).expect("send buffer failed"); + return Ok(()); + } + + /// receive a buffer from the EC2 instance via sockets, using `socket_fd` + pub fn receive_buffer(&mut self) -> Result, EC2Error> { + let socket_fd = match self.socket_fd { + Some(socket_fd) => socket_fd, + None => { + println!("EC2Instance::receive_buffer connecting socket. I don't think this should happen"); + self.socket_connect()? + } + }; + let received_buffer = + veracruz_utils::receive_buffer(socket_fd).expect("Failed to receive buffer"); + return Ok(received_buffer); + } +} + +impl Drop for EC2Instance { + /// Drop the EC2 instance, which calls `close` and terminates the instance + fn drop(&mut self) { + match self.close() { + Err(err) => println!("EC2Instance::drop failed on call to close:{:?}", err), + _ => (), + } + } +} diff --git a/sinaloa/src/lib.rs b/sinaloa/src/lib.rs index 03f152261..a9a0b82a7 100644 --- a/sinaloa/src/lib.rs +++ b/sinaloa/src/lib.rs @@ -24,3 +24,11 @@ pub use self::sinaloa_tz::sinaloa_tz::*; pub mod sinaloa_sgx; #[cfg(feature = "sgx")] pub use self::sinaloa_sgx::sinaloa_sgx::*; + +#[cfg(feature = "nitro")] +pub mod sinaloa_nitro; +#[cfg(feature = "nitro")] +pub use self::sinaloa_nitro::sinaloa_nitro::*; + +#[cfg(feature = "nitro")] +mod ec2_instance; diff --git a/sinaloa/src/server.rs b/sinaloa/src/server.rs index 49b1a709c..65b08c6df 100644 --- a/sinaloa/src/server.rs +++ b/sinaloa/src/server.rs @@ -10,10 +10,13 @@ //! information on licensing and copyright. use crate::sinaloa::*; +#[cfg(feature = "nitro")] +use crate::sinaloa_nitro::sinaloa_nitro::SinaloaNitro as SinaloaEnclave; #[cfg(feature = "sgx")] -use crate::sinaloa_sgx::sinaloa_sgx::SinaloaSGX as Sinaloa_encalve; +use crate::sinaloa_sgx::sinaloa_sgx::SinaloaSGX as SinaloaEnclave; #[cfg(feature = "tz")] -use crate::sinaloa_tz::sinaloa_tz::SinaloaTZ as Sinaloa_encalve; +use crate::sinaloa_tz::sinaloa_tz::SinaloaTZ as SinaloaEnclave; + use actix_web::{dev::Server, middleware, post, web, App, HttpRequest, HttpServer}; use base64; use futures::executor; @@ -103,7 +106,7 @@ pub fn server(policy_filename: &str) -> Result { let policy_json = std::fs::read_to_string(policy_filename)?; let policy: veracruz_utils::VeracruzPolicy = serde_json::from_str(policy_json.as_str())?; #[allow(non_snake_case)] - let SINALOA: EnclaveHandler = Arc::new(Mutex::new(Some(Box::new(Sinaloa_encalve::new( + let SINALOA: EnclaveHandler = Arc::new(Mutex::new(Some(Box::new(SinaloaEnclave::new( &policy_json, )?)))); diff --git a/sinaloa/src/sinaloa.rs b/sinaloa/src/sinaloa.rs index 325a1db6b..78acd9af6 100644 --- a/sinaloa/src/sinaloa.rs +++ b/sinaloa/src/sinaloa.rs @@ -9,12 +9,16 @@ //! See the `LICENSE.markdown` file in the Veracruz root directory for //! information on licensing and copyright. +#[cfg(feature = "nitro")] +use crate::ec2_instance::EC2Error; use actix_http::ResponseBuilder; use actix_web::{error, http::StatusCode, HttpResponse}; use curl::easy::{Easy, List}; use err_derive::Error; use log::debug; use std::io::Read; +#[cfg(feature = "nitro")] +use veracruz_utils::nitro_enclave::NitroError; pub type SinaloaResponder = Result; @@ -71,6 +75,42 @@ pub enum SinaloaError { #[cfg(feature = "sgx")] #[error(display = "Sinaloa: SGXError: {:?}.", _0)] SGXError(sgx_types::sgx_status_t), + #[cfg(feature = "nitro")] + #[error(display = "Sinaloa: BincodeError: {:?}", _0)] + BincodeError(bincode::ErrorKind), + #[cfg(feature = "nitro")] + #[error(display = "Sinaloa: MCMessage::Status: {:?}", _0)] + MCMessageStatus(veracruz_utils::MCMessage), + #[cfg(feature = "nitro")] + #[error(display = "Sinaloa: NitroStatus: {:?}", _0)] + NitroStatus(veracruz_utils::NitroStatus), + #[cfg(feature = "nitro")] + #[error(display = "Sinaloa: Received Invalid MC Message: {:?}", _0)] + InvalidMCMessage(veracruz_utils::MCMessage), + #[cfg(feature = "nitro")] + #[error( + display = "Sinaloa: Received Invalid Nitro Root Enclave Message: {:?}", + _0 + )] + InvalidNitroRootEnclaveMessage(veracruz_utils::NitroRootEnclaveMessage), + #[cfg(feature = "nitro")] + #[error(display = "Sinaloa: Received Invalid Protocol Buffer Message")] + InvalidProtoBufMessage, + #[cfg(feature = "nitro")] + #[error(display = "Sinaloa: Nix Error: {:?}", _0)] + NixError(#[error(source)] nix::Error), + #[cfg(feature = "nitro")] + #[error(display = "Sinaloa: Serde Error")] + SerdeError, + #[cfg(feature = "nitro")] + #[error(display = "Sinaloa: Veracruz Socket Error:{:?}", _0)] + VeracruzSocketError(#[error(source)] veracruz_utils::VeracruzSocketError), + #[cfg(feature = "nitro")] + #[error(display = "Sinaloa: Nitro Error:{:?}", _0)] + NitroError(#[error(source)] NitroError), + #[cfg(feature = "nitro")] + #[error(display = "Sinaloa: EC2 Error:{:?}", _0)] + EC2Error(#[error(source)] EC2Error), #[cfg(feature = "tz")] #[error(display = "Sinaloa: UUIDError: {:?}.", _0)] UUIDError(#[error(source)] uuid::parser::ParseError), @@ -127,6 +167,8 @@ pub enum SinaloaError { DirectMessageError(String, StatusCode), #[error(display = "Sinaloa: Error message {}.", _0)] DirectStrError(&'static str), + #[error(display = "Sinaloa: Unimplemented")] + UnimplementedError, } impl From> for SinaloaError { @@ -162,6 +204,13 @@ impl error::ResponseError for SinaloaError { } } +#[cfg(feature = "nitro")] +impl From> for SinaloaError { + fn from(error: std::boxed::Box) -> Self { + SinaloaError::BincodeError(*error) + } +} + pub trait Sinaloa { fn new(policy: &str) -> Result where @@ -249,6 +298,10 @@ pub fn post_buffer(url: &str, buffer: &String) -> Result { let lines = received_header.split("\n"); lines.collect() }; + println!( + "sinaloa::send_tabasco_start received header:{:?}", + received_header + ); if !received_header.contains("HTTP/1.1 200 OK\r") { return Err(SinaloaError::ReceivedNonSuccessPostStatusError); } diff --git a/sinaloa/src/sinaloa_nitro.rs b/sinaloa/src/sinaloa_nitro.rs new file mode 100644 index 000000000..ecd5335f1 --- /dev/null +++ b/sinaloa/src/sinaloa_nitro.rs @@ -0,0 +1,378 @@ +//! Nitro-Enclave-specific material for Sinaloa +//! +//! ## Authors +//! +//! The Veracruz Development Team. +//! +//! ## Licensing and copyright notice +//! +//! See the `LICENSE.markdown` file in the Veracruz root directory for +//! information on licensing and copyright. + +#[cfg(feature = "nitro")] +pub mod sinaloa_nitro { + use crate::ec2_instance::EC2Instance; + use crate::sinaloa::Sinaloa; + use crate::sinaloa::SinaloaError; + use lazy_static::lazy_static; + use std::sync::Mutex; + use veracruz_utils::{ + policy::EnclavePlatform, MCMessage, NitroEnclave, NitroError, NitroStatus, + }; + + const MEXICO_CITY_EIF_PATH: &str = "../mexico-city/mexico_city.eif"; + const NITRO_ROOT_ENCLAVE_EIF_PATH: &str = "../nitro-root-enclave/nitro_root_enclave.eif"; + const NITRO_ROOT_ENCLAVE_SERVER_PATH: &str = + "../nitro-root-enclave-server/target/debug/nitro-root-enclave-server"; + + lazy_static! { + //static ref NRE_CONTEXT: Mutex> = Mutex::new(None); + static ref NRE_CONTEXT: Mutex> = Mutex::new(None); + } + + pub struct SinaloaNitro { + enclave: NitroEnclave, + } + + impl Sinaloa for SinaloaNitro { + fn new(policy_json: &str) -> Result { + // Set up, initialize Nitro Root Enclave + let policy: veracruz_utils::VeracruzPolicy = + veracruz_utils::VeracruzPolicy::from_json(policy_json)?; + + { + let mut nre_guard = NRE_CONTEXT.lock()?; + if nre_guard.is_none() { + println!("NITRO ROOT ENCLAVE IS UNINITIALIZED."); + let mexico_city_hash = policy + .mexico_city_hash(&EnclavePlatform::Nitro) + .map_err(|err| SinaloaError::VeracruzUtilError(err))?; + let nre_context = + SinaloaNitro::native_attestation(&policy.tabasco_url(), &mexico_city_hash)?; + *nre_guard = Some(nre_context); + } + } + + println!("SinaloaNitro::new native_attestation complete. instantiating Mexico City"); + #[cfg(feature = "debug")] + let mexico_city_enclave = { + println!("Starting mexico city enclave in debug mode"); + NitroEnclave::new( + false, + MEXICO_CITY_EIF_PATH, + true, + Some(SinaloaNitro::sinaloa_ocall_handler), + ) + .map_err(|err| SinaloaError::NitroError(err))? + }; + #[cfg(not(feature = "debug"))] + let mexico_city_enclave = { + println!("Starting mexico city enclave in release mode"); + NitroEnclave::new( + false, + MEXICO_CITY_EIF_PATH, + false, + Some(SinaloaNitro::sinaloa_ocall_handler), + ) + .map_err(|err| SinaloaError::NitroError(err))? + }; + println!("SinaloaNitro::new NitroEnclave::new returned"); + let meta = Self { + enclave: mexico_city_enclave, + }; + println!("SinaloaNitro::new Mexico City instantiated. Calling initialize"); + std::thread::sleep(std::time::Duration::from_millis(10000)); + + let initialize: MCMessage = MCMessage::Initialize(policy_json.to_string()); + + let encoded_buffer: Vec = bincode::serialize(&initialize)?; + meta.enclave.send_buffer(&encoded_buffer)?; + + // read the response + let status_buffer = meta.enclave.receive_buffer()?; + + let message: MCMessage = bincode::deserialize(&status_buffer[..])?; + let status = match message { + MCMessage::Status(status) => status, + _ => return Err(SinaloaError::MCMessageStatus(message)), + }; + match status { + NitroStatus::Success => (), + _ => return Err(SinaloaError::NitroStatus(status)), + } + println!("SinaloaNitro::new complete. Returning"); + return Ok(meta); + } + + fn plaintext_data(&self, data: Vec) -> Result>, SinaloaError> { + let parsed = colima::parse_mexico_city_request(&data)?; + + if parsed.has_request_proxy_psa_attestation_token() { + let rpat = parsed.get_request_proxy_psa_attestation_token(); + let challenge = colima::parse_request_proxy_psa_attestation_token(rpat); + let (psa_attestation_token, pubkey, device_id) = + self.proxy_psa_attestation_get_token(challenge)?; + let serialized_pat = colima::serialize_proxy_psa_attestation_token( + &psa_attestation_token, + &pubkey, + device_id, + )?; + Ok(Some(serialized_pat)) + } else { + return Err(SinaloaError::InvalidProtoBufMessage); + } + } + + // Note: this function will go away + fn get_enclave_cert(&self) -> Result, SinaloaError> { + let certificate = { + let message = MCMessage::GetEnclaveCert; + let message_buffer = bincode::serialize(&message)?; + self.enclave.send_buffer(&message_buffer)?; + // Read the resulting data as the certificate + let received_buffer = self.enclave.receive_buffer()?; + let received_message: MCMessage = bincode::deserialize(&received_buffer)?; + match received_message { + MCMessage::EnclaveCert(cert) => cert, + _ => return Err(SinaloaError::InvalidMCMessage(received_message))?, + } + }; + return Ok(certificate); + } + + // Note: This function will go away + fn get_enclave_name(&self) -> Result { + let name: String = { + let message = MCMessage::GetEnclaveName; + let message_buffer = bincode::serialize(&message)?; + self.enclave.send_buffer(&message_buffer)?; + // Read the resulting data as the name + let received_buffer = self.enclave.receive_buffer()?; + let received_message: MCMessage = bincode::deserialize(&received_buffer)?; + match received_message { + MCMessage::EnclaveName(name) => name, + _ => return Err(SinaloaError::InvalidMCMessage(received_message)), + } + }; + return Ok(name); + } + + fn proxy_psa_attestation_get_token( + &self, + challenge: Vec, + ) -> Result<(Vec, Vec, i32), SinaloaError> { + let message = MCMessage::GetPSAAttestationToken(challenge); + let message_buffer = bincode::serialize(&message)?; + self.enclave.send_buffer(&message_buffer)?; + + let received_buffer = self.enclave.receive_buffer()?; + let received_message: MCMessage = bincode::deserialize(&received_buffer)?; + let (token, public_key, device_id) = match received_message { + MCMessage::PSAAttestationToken(token, public_key, device_id) => { + (token, public_key, device_id) + } + _ => return Err(SinaloaError::InvalidMCMessage(received_message)), + }; + return Ok((token, public_key, device_id)); + } + + fn new_tls_session(&self) -> Result { + let nls_message = MCMessage::NewTLSSession; + let nls_buffer = bincode::serialize(&nls_message)?; + self.enclave.send_buffer(&nls_buffer)?; + + let received_buffer: Vec = self.enclave.receive_buffer()?; + + let received_message: MCMessage = bincode::deserialize(&received_buffer)?; + let session_id = match received_message { + MCMessage::TLSSession(sid) => sid, + _ => return Err(SinaloaError::InvalidMCMessage(received_message)), + }; + return Ok(session_id); + } + + fn close_tls_session(&self, session_id: u32) -> Result<(), SinaloaError> { + let cts_message = MCMessage::CloseTLSSession(session_id); + let cts_buffer = bincode::serialize(&cts_message)?; + + self.enclave.send_buffer(&cts_buffer)?; + + let received_buffer: Vec = self.enclave.receive_buffer()?; + + let received_message: MCMessage = bincode::deserialize(&received_buffer)?; + return match received_message { + MCMessage::Status(_status) => Ok(()), + _ => Err(SinaloaError::NitroStatus(NitroStatus::Fail)), + }; + } + + fn tls_data( + &self, + session_id: u32, + input: Vec, + ) -> Result<(bool, Option>>), SinaloaError> { + let std_message: MCMessage = MCMessage::SendTLSData(session_id, input); + let std_buffer: Vec = bincode::serialize(&std_message)?; + + self.enclave.send_buffer(&std_buffer)?; + + let received_buffer: Vec = self.enclave.receive_buffer()?; + + let received_message: MCMessage = bincode::deserialize(&received_buffer)?; + match received_message { + MCMessage::Status(status) => match status { + NitroStatus::Success => (), + _ => return Err(SinaloaError::NitroStatus(status)), + }, + _ => return Err(SinaloaError::InvalidMCMessage(received_message)), + } + + let mut active_flag = true; + let mut ret_array = Vec::new(); + while self.tls_data_needed(session_id)? { + let gtd_message = MCMessage::GetTLSData(session_id); + let gtd_buffer: Vec = bincode::serialize(>d_message)?; + + self.enclave.send_buffer(>d_buffer)?; + + let received_buffer: Vec = self.enclave.receive_buffer()?; + + let received_message: MCMessage = bincode::deserialize(&received_buffer)?; + match received_message { + MCMessage::TLSData(data, alive) => { + active_flag = alive; + ret_array.push(data); + } + _ => return Err(SinaloaError::NitroStatus(NitroStatus::Fail)), + } + } + + Ok(( + active_flag, + if ret_array.len() > 0 { + Some(ret_array) + } else { + None + }, + )) + } + + fn close(&mut self) -> Result { + let re_message: MCMessage = MCMessage::ResetEnclave; + let re_buffer: Vec = bincode::serialize(&re_message)?; + + self.enclave.send_buffer(&re_buffer)?; + + let received_buffer: Vec = self.enclave.receive_buffer()?; + let received_message: MCMessage = bincode::deserialize(&received_buffer)?; + return match received_message { + MCMessage::Status(status) => match status { + NitroStatus::Success => Ok(true), + _ => Err(SinaloaError::NitroStatus(status)), + }, + _ => Err(SinaloaError::InvalidMCMessage(received_message)), + }; + } + } + + impl Drop for SinaloaNitro { + fn drop(&mut self) { + match self.close() { + Err(err) => println!("SinaloaNitro::drop failed in call to self.close:{:?}, we will persevere, though.", err), + _ => (), + } + } + } + + impl SinaloaNitro { + fn sinaloa_ocall_handler(input_buffer: Vec) -> Result, NitroError> { + let return_buffer: Vec = { + let mut nre_guard = NRE_CONTEXT.lock().map_err(|_| NitroError::MutexError)?; + match &mut *nre_guard { + Some(nre) => { + nre.send_buffer(&input_buffer).map_err(|err| { + println!( + "SinaloaNitro::sinaloa_ocall_handler send_buffer failed:{:?}", + err + ); + NitroError::EC2Error + })?; + let ret_buffer = nre.receive_buffer().map_err(|err| { + println!( + "SinaloaNitro::sinaloa_ocall_handler receive_buffer failed:{:?}", + err + ); + NitroError::EC2Error + })?; + ret_buffer + } + None => return Err(NitroError::EC2Error), + } + }; + return Ok(return_buffer); + } + + fn tls_data_needed(&self, session_id: u32) -> Result { + let gtdn_message = MCMessage::GetTLSDataNeeded(session_id); + let gtdn_buffer: Vec = bincode::serialize(>dn_message)?; + + self.enclave.send_buffer(>dn_buffer)?; + + let received_buffer: Vec = self.enclave.receive_buffer()?; + + let received_message: MCMessage = bincode::deserialize(&received_buffer)?; + let tls_data_needed = match received_message { + MCMessage::TLSDataNeeded(needed) => needed, + _ => return Err(SinaloaError::NitroStatus(NitroStatus::Fail)), + }; + return Ok(tls_data_needed); + } + + fn native_attestation( + tabasco_url: &str, + _mexico_city_hash: &str, + //) -> Result { + ) -> Result { + println!("SinaloaNitro::native_attestation started"); + + println!("Starting EC2 instance"); + let nre_instance = EC2Instance::new().map_err(|err| SinaloaError::EC2Error(err))?; + + nre_instance + .upload_file( + NITRO_ROOT_ENCLAVE_EIF_PATH, + "/home/ec2-user/nitro_root_enclave.eif", + ) + .map_err(|err| SinaloaError::EC2Error(err))?; + nre_instance + .upload_file( + NITRO_ROOT_ENCLAVE_SERVER_PATH, + "/home/ec2-user/nitro-root-enclave-server", + ) + .map_err(|err| SinaloaError::EC2Error(err))?; + + nre_instance + .execute_command("nitro-cli-config -t 2 -m 512") + .map_err(|err| SinaloaError::EC2Error(err))?; + #[cfg(feature = "debug")] + let server_command: String = format!( + "nohup /home/ec2-user/nitro-root-enclave-server --debug {:} &> nitro_server.log &", + tabasco_url + ); + #[cfg(not(feature = "debug"))] + let server_command: String = format!( + "nohup /home/ec2-user/nitro-root-enclave-server {:} &> nitro_server.log &", + tabasco_url + ); + nre_instance + .execute_command(&server_command) + .map_err(|err| SinaloaError::EC2Error(err))?; + + println!("Waiting for NRE Instance to authenticate."); + std::thread::sleep(std::time::Duration::from_millis(15000)); + + println!("sinaloa_tz::native_attestation returning Ok"); + return Ok(nre_instance); + } + } +} diff --git a/sinaloa/src/sinaloa_tz.rs b/sinaloa/src/sinaloa_tz.rs index 9b99e4c60..b05e5d626 100644 --- a/sinaloa/src/sinaloa_tz.rs +++ b/sinaloa/src/sinaloa_tz.rs @@ -21,7 +21,7 @@ pub mod sinaloa_tz { }; use std::convert::TryInto; use std::sync::Mutex; - use veracruz_utils::{JaliscoOpcode, MCOpcode, JALISCO_UUID, MC_UUID}; + use veracruz_utils::{EnclavePlatform, JaliscoOpcode, MCOpcode, JALISCO_UUID, MC_UUID}; lazy_static! { static ref CONTEXT: Mutex> = Mutex::new(Some(Context::new().unwrap())); @@ -41,13 +41,19 @@ pub mod sinaloa_tz { let jalisco_uuid = Uuid::parse_str(&JALISCO_UUID.to_string())?; { + let mexico_city_hash = { + match policy.mexico_city_hash(&EnclavePlatform::TrustZone) { + Ok(hash) => hash, + Err(_) => return Err(SinaloaError::MissingFieldError("mexico_city_hash_tz")), + } + }; let mut ji_guard = JALISCO_INITIALIZED.lock()?; if !*ji_guard { debug!("Jalisco is uninitialized."); SinaloaTZ::native_attestation( &policy.tabasco_url(), jalisco_uuid, - &policy.mexico_city_hash(), + &mexico_city_hash, )?; *ji_guard = true; } diff --git a/tabasco/Cargo.toml b/tabasco/Cargo.toml index fb99a47c8..9eb3d6a0e 100644 --- a/tabasco/Cargo.toml +++ b/tabasco/Cargo.toml @@ -24,6 +24,7 @@ sgx = ["sgx_types", "sgx_ucrypto", "sgx_urts", "psa-attestation/sgx", "colima/sg # Note: Final attestation is always PSA. This is just to enable platforms # that use PSA for their native attestation. psa = ["psa-attestation/tz"] +nitro = [ "serde_cbor", "nitro-enclave-token" ] [dependencies] rouille = "3.0" @@ -40,7 +41,7 @@ diesel = { version = "1.0.0", features = ["sqlite", "numeric" ] } dotenv = "0.9.0" hex = "0.3" psa-attestation = { path = "../psa-attestation" } -ring = "0.16" +ring = { git = "https://github.com/veracruz-project/ring.git", branch = "veracruz" } futures = "0.3" veracruz-utils = { path = "../veracruz-utils", features = ["std"] } actix-web = "2.0.0" @@ -49,6 +50,9 @@ actix-http = "1.0" async-std = "1.5" env_logger = "0.7" err-derive = "0.2" +serde_cbor = {version = "0.11", optional = true } +nitro-enclave-token = { git = "https://github.com/veracruz-project/nitro-enclave-token.git", branch = "main", optional = true } + [target.'cfg(target_arch = "x86_64")'.dependencies] sgx_types = { rev = "v1.1.2", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } diff --git a/tabasco/src/attestation.rs b/tabasco/src/attestation.rs index d2e8831b6..d00a73f7d 100644 --- a/tabasco/src/attestation.rs +++ b/tabasco/src/attestation.rs @@ -13,6 +13,8 @@ pub mod psa; #[cfg(feature = "sgx")] pub mod sgx; +#[cfg(feature = "nitro")] +pub mod nitro; use crate::error::*; use lazy_static::lazy_static; @@ -23,17 +25,30 @@ lazy_static! { } pub async fn start(body_string: String) -> TabascoResponder { - let received_bytes = base64::decode(&body_string)?; + let received_bytes = base64::decode(&body_string) + .map_err(|err| { + println!("tabasco::attestation::start failed to decode body_string as base64:{:?}", err); + err + })?; - let parsed = colima::parse_tabasco_request(&received_bytes)?; + let parsed = colima::parse_tabasco_request(&received_bytes) + .map_err(|err| { + println!("tabasco::attestation::start failed to parse_tabasco_request:{:?}", err); + err + })?; if !parsed.has_start_msg() { + println!("Tabasco::attestation::start it don't have start_msg"); return Err(TabascoError::MissingFieldError("start msg")); } let (protocol, firmware_version) = colima::parse_start_msg(&parsed); let device_id = { - let mut device_id_wrapper = DEVICE_ID.lock()?; + let mut device_id_wrapper = DEVICE_ID.lock() + .map_err(|err| { + println!("tabasco::attestation::start failed to obtain lock on DEVICE_ID:{:?}", err); + err + })?; *device_id_wrapper = *device_id_wrapper + 1; *device_id_wrapper }; @@ -43,6 +58,8 @@ pub async fn start(body_string: String) -> TabascoResponder { "sgx" => sgx::start(&firmware_version, device_id), #[cfg(feature = "psa")] "psa" => psa::start(&firmware_version, device_id), + #[cfg(feature = "nitro")] + "nitro" => nitro::start(&firmware_version, device_id), _ => Err(TabascoError::UnknownAttestationTokenError), } } diff --git a/tabasco/src/attestation/nitro.rs b/tabasco/src/attestation/nitro.rs new file mode 100644 index 000000000..f88e92dde --- /dev/null +++ b/tabasco/src/attestation/nitro.rs @@ -0,0 +1,236 @@ +//! AWS Nitro specific material for Tabasco +//! +//! ## Authors +//! +//! The Veracruz Development Team. +//! +//! ## Licensing and copyright notice +//! +//! See the `LICENSE.markdown` file in the Veracruz root directory for +//! information on licensing and copyright. + +use crate::error::*; +use lazy_static::lazy_static; +use rand::Rng; +use std::{collections::HashMap, sync::Mutex}; +use std::io::Write; +use std::sync::atomic::Ordering; + +use nitro_enclave_token::NitroToken; + +/// The DER-encoded root certificate used to authenticate the certificate chain +/// (which is used to authenticate the Nitro Enclave tokens). +/// AWS claims that this certificate should never change (read: only change if +/// they have an extremely serious security issue), and it's expiry is set to +/// some time in 2049. +static AWS_NITRO_ROOT_CERTIFICATE: [u8; 533] = [ + 0x30, 0x82, 0x02, 0x11, 0x30, 0x82, 0x01, 0x96, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x11, 0x00, + 0xf9, 0x31, 0x75, 0x68, 0x1b, 0x90, 0xaf, 0xe1, 0x1d, 0x46, 0xcc, 0xb4, 0xe4, 0xe7, 0xf8, 0x56, + 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x30, 0x49, 0x31, 0x0b, + 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0f, 0x30, 0x0d, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x0c, 0x30, 0x0a, + 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x03, 0x41, 0x57, 0x53, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0c, 0x12, 0x61, 0x77, 0x73, 0x2e, 0x6e, 0x69, 0x74, 0x72, 0x6f, 0x2d, 0x65, + 0x6e, 0x63, 0x6c, 0x61, 0x76, 0x65, 0x73, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x30, 0x32, + 0x38, 0x31, 0x33, 0x32, 0x38, 0x30, 0x35, 0x5a, 0x17, 0x0d, 0x34, 0x39, 0x31, 0x30, 0x32, 0x38, + 0x31, 0x34, 0x32, 0x38, 0x30, 0x35, 0x5a, 0x30, 0x49, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, + 0x06, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0b, + 0x0c, 0x03, 0x41, 0x57, 0x53, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x12, + 0x61, 0x77, 0x73, 0x2e, 0x6e, 0x69, 0x74, 0x72, 0x6f, 0x2d, 0x65, 0x6e, 0x63, 0x6c, 0x61, 0x76, + 0x65, 0x73, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, + 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0xfc, 0x02, 0x54, 0xeb, 0xa6, 0x08, + 0xc1, 0xf3, 0x68, 0x70, 0xe2, 0x9a, 0xda, 0x90, 0xbe, 0x46, 0x38, 0x32, 0x92, 0x73, 0x6e, 0x89, + 0x4b, 0xff, 0xf6, 0x72, 0xd9, 0x89, 0x44, 0x4b, 0x50, 0x51, 0xe5, 0x34, 0xa4, 0xb1, 0xf6, 0xdb, + 0xe3, 0xc0, 0xbc, 0x58, 0x1a, 0x32, 0xb7, 0xb1, 0x76, 0x07, 0x0e, 0xde, 0x12, 0xd6, 0x9a, 0x3f, + 0xea, 0x21, 0x1b, 0x66, 0xe7, 0x52, 0xcf, 0x7d, 0xd1, 0xdd, 0x09, 0x5f, 0x6f, 0x13, 0x70, 0xf4, + 0x17, 0x08, 0x43, 0xd9, 0xdc, 0x10, 0x01, 0x21, 0xe4, 0xcf, 0x63, 0x01, 0x28, 0x09, 0x66, 0x44, + 0x87, 0xc9, 0x79, 0x62, 0x84, 0x30, 0x4d, 0xc5, 0x3f, 0xf4, 0xa3, 0x42, 0x30, 0x40, 0x30, 0x0f, + 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, + 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x90, 0x25, 0xb5, 0x0d, 0xd9, 0x05, + 0x47, 0xe7, 0x96, 0xc3, 0x96, 0xfa, 0x72, 0x9d, 0xcf, 0x99, 0xa9, 0xdf, 0x4b, 0x96, 0x30, 0x0e, + 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x0a, + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x03, 0x69, 0x00, 0x30, 0x66, 0x02, + 0x31, 0x00, 0xa3, 0x7f, 0x2f, 0x91, 0xa1, 0xc9, 0xbd, 0x5e, 0xe7, 0xb8, 0x62, 0x7c, 0x16, 0x98, + 0xd2, 0x55, 0x03, 0x8e, 0x1f, 0x03, 0x43, 0xf9, 0x5b, 0x63, 0xa9, 0x62, 0x8c, 0x3d, 0x39, 0x80, + 0x95, 0x45, 0xa1, 0x1e, 0xbc, 0xbf, 0x2e, 0x3b, 0x55, 0xd8, 0xae, 0xee, 0x71, 0xb4, 0xc3, 0xd6, + 0xad, 0xf3, 0x02, 0x31, 0x00, 0xa2, 0xf3, 0x9b, 0x16, 0x05, 0xb2, 0x70, 0x28, 0xa5, 0xdd, 0x4b, + 0xa0, 0x69, 0xb5, 0x01, 0x6e, 0x65, 0xb4, 0xfb, 0xde, 0x8f, 0xe0, 0x06, 0x1d, 0x6a, 0x53, 0x19, + 0x7f, 0x9c, 0xda, 0xf5, 0xd9, 0x43, 0xbc, 0x61, 0xfc, 0x2b, 0xeb, 0x03, 0xcb, 0x6f, 0xee, 0x8d, + 0x23, 0x02, 0xf3, 0xdf, 0xf6, +]; + +/// A struct containing information needed for attestation of a specific +/// Nitro Root enclave +#[derive(Clone)] +struct NitroAttestationContext { + /// The firmware version of the Nitro Root Enclave + firmware_version: String, + /// The challenge that we sent to the Nitro Root Enclave (used + /// when authenticating it's token) + challenge: [u8; 32], +} + +lazy_static! { + /// A hash map containing a `NitroAttestationContext` for each of the + /// Nitro Root enclaves that we have started native attestation for + static ref ATTESTATION_CONTEXT: Mutex> = + Mutex::new(HashMap::new()); +} + +/// Start the Nitro enclave attestation process for an enclave with the +/// provided firmware version and the provided `device_id`. +/// Note that this is the `device_id` we sent with the challenge. +pub fn start(firmware_version: &str, device_id: i32) -> TabascoResponder { + let mut challenge: [u8; 32] = [0; 32]; + let mut rng = rand::thread_rng(); + + rng.fill(&mut challenge); + + let attestation_context = NitroAttestationContext { + firmware_version: firmware_version.to_string(), + challenge: challenge.clone(), + }; + { + let mut ac_hash = ATTESTATION_CONTEXT.lock()?; + ac_hash.insert(device_id, attestation_context); + } + let serialized_attestation_init = + colima::serialize_psa_attestation_init(&challenge, device_id)?; + Ok(base64::encode(&serialized_attestation_init)) +} + +/// Handle an attestation token passed to us in the `body_string` parameter +pub fn attestation_token(body_string: String) -> TabascoResponder { + + let received_bytes = base64::decode(&body_string) + .map_err(|err| { + println!("tabasco::attestation::nitro::attestation_token failed to decode base64:{:?}", err); + let _ignore = std::io::stdout().flush(); + err + })?; + + let parsed = colima::parse_tabasco_request(&received_bytes) + .map_err(|err| { + println!("tabasco::attestation::nitro::attestation_token failed to parse tabasco request:{:?}", err); + let _ignore = std::io::stdout().flush(); + err + })?; + if !parsed.has_native_psa_attestation_token() { + println!("tabasco::attestation::psa::attestation_token received data is incorrect."); + let _ignore = std::io::stdout().flush(); + return Err(TabascoError::MissingFieldError( + "native_psa_attestation_token", + )); + } + let (token, device_id) = + colima::parse_native_psa_attestation_token(&parsed.get_native_psa_attestation_token()); + + let attestation_document = NitroToken::authenticate_token(&token, &AWS_NITRO_ROOT_CERTIFICATE).map_err(|err| { + println!("Tabasco::nitro::attestation_token authenticate_token failed:{:?}", err); + let _ignore = std::io::stdout().flush(); + TabascoError::CborError(format!("parse_nitro_token failed to parse token data:{:?}", err)) + })?; + + let attestation_context = { + let ac_hash = ATTESTATION_CONTEXT.lock() + .map_err(|err| { + println!("Tabasco::nitro::attestation_token failed to obtain lock on ATTESTATION_CONTEXT:{:?}", err); + let _ignore = std::io::stdout().flush(); + err + })?; + println!("ac_hash:{:?}", ac_hash[&device_id].firmware_version); + if ac_hash.contains_key(&device_id) { + let context = &ac_hash[&device_id]; + context.clone() + } else { + println!("Tabasco::nitro::attestation_token device not found. device_id:{:?}", device_id); + let _ignore = std::io::stdout().flush(); + return Err(TabascoError::NoDeviceError(device_id)); + } + }; + + // check the nonce of the attestation document + match attestation_document.nonce { + None => { + println!("tabasco::attestation::nitro::attestation_token attestation document did not contain a nonce. We require it."); + let _ignore = std::io::stdout().flush(); + return Err(TabascoError::MissingFieldError("nonce")); + }, + Some(nonce) => { + if nonce != attestation_context.challenge { + println!("Challenge failed to match. Wanted:{:02x?}, got:{:02x?}", nonce, attestation_context.challenge); + let _ignore = std::io::stdout().flush(); + return Err(TabascoError::MismatchError { + variable: "nonce/challenge", + expected: attestation_context.challenge.to_vec(), + received: nonce, + }); + } + }, + } + + + let expected_enclave_hash: Vec = { + let connection = crate::orm::establish_connection()?; + let hash_option = crate::orm::get_firmware_version_hash( + &connection, + &"nitro".to_string(), + &attestation_context.firmware_version, + ) + .map_err(|err| { + println!("tabasco::attestation::nitro::attestation_token get_firmware_version_hash failed:{:?}", err); + let _ignore = std::io::stdout().flush(); + err + })?; + match hash_option { + None => { + println!("tabasco::attestation::nitro_attestation_token firmware version hash not found in database"); + let _ignore = std::io::stdout().flush(); + return Err(TabascoError::MissingFieldError("firmware version")); + }, + Some(hash) => hash, + } + }; + let received_enclave_hash = &attestation_document.pcrs[0]; + if expected_enclave_hash != *received_enclave_hash { + if crate::server::DEBUG_MODE.load(Ordering::Relaxed) { + println!("Comparison between expected_enclave_hash:{:02x?} and received_enclave_hash:{:02x?} failed", expected_enclave_hash, *received_enclave_hash); + println!("This is debug mode, so this is expected, so we're not going to fail you, but you should feel bad."); + let _ignore = std::io::stdout().flush(); + } else { + println!("tabasco::attestation::nitro_attestation_token debug mode is off, so we're gonna return an error for this mismatch"); + let _ignore = std::io::stdout().flush(); + + return Err(TabascoError::MismatchError { + variable: "received_enclave_hash", + expected: expected_enclave_hash, + received: received_enclave_hash.to_vec(), + }); + } + } + + let digest = match attestation_document.public_key { + Some(public_key) => ring::digest::digest(&ring::digest::SHA256, &public_key), + None => { + return Err(TabascoError::MissingFieldError("public_key")); + }, + }; + let pubkey_hash = digest.as_ref(); + + // TODO: Get a real enclave name (or just get rid of the enclave names entirely) + let enclave_name: String = "bob".to_string(); + + let connection = crate::orm::establish_connection()?; + crate::orm::update_or_create_device( + &connection, + device_id, + &pubkey_hash.to_vec(), + enclave_name, + ).map_err(|err| { + println!("nitro:: failed to add device to database:{:?}", err); + let _ignore = std::io::stdout().flush(); + err + })?; + Ok("Pass".to_string()) +} diff --git a/tabasco/src/attestation/sgx.rs b/tabasco/src/attestation/sgx.rs index 498c6dbf1..1cf0bf768 100644 --- a/tabasco/src/attestation/sgx.rs +++ b/tabasco/src/attestation/sgx.rs @@ -53,9 +53,21 @@ static QUOTE_TYPE: u16 = 1; //SAMPLE_QUOTE_LINKABLE_SIGNATURE pub fn start(firmware_version: &str, device_id: i32) -> TabascoResponder { let sgx_ecc_handle = SgxEccHandle::new(); - sgx_ecc_handle.open()?; - let (private_key, public_key) = sgx_ecc_handle.create_key_pair()?; - sgx_ecc_handle.close()?; + sgx_ecc_handle.open() + .map_err(|err| { + println!("tabasco::attestation::sgx::start failed to open sgx_ecc_handle:{:?}", err); + err + })?; + let (private_key, public_key) = sgx_ecc_handle.create_key_pair() + .map_err(|err| { + println!("tabasco::attestation::sgx::start failed to create key pair:{:?}", err); + err + })?; + sgx_ecc_handle.close() + .map_err(|err| { + println!("tabasco::attestation::sgx::start failed to close sgx_ecc_Handle:{:?}", err); + err + })?; let mut serialized_pubkey = Vec::new(); @@ -72,18 +84,34 @@ pub fn start(firmware_version: &str, device_id: i32) -> TabascoResponder { msg2: None, pubkey_challenge: None, }; - let mut ac_hash = ATTESTATION_CONTEXT.lock()?; + let mut ac_hash = ATTESTATION_CONTEXT.lock() + .map_err(|err| { + println!("tabasco::attestation::sgx::start failed to obtain lock on ATTESTATION_CONTEXT:{:?}", err); + err + })?; ac_hash.insert(device_id, attestation_context); } let serialized_attestation_init = - colima::serialize_sgx_attestation_init(&serialized_pubkey, device_id)?; + colima::serialize_sgx_attestation_init(&serialized_pubkey, device_id) + .map_err(|err| { + println!("tabasco::attestation::sgx::start serialize_sgx_attestation_init failed:{:?}", err); + err + })?; Ok(base64::encode(&serialized_attestation_init)) } pub fn msg1(body_string: String) -> TabascoResponder { - let received_bytes = base64::decode(&body_string)?; + let received_bytes = base64::decode(&body_string) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg1 failed to decode body_string as base64:{:?}", err); + err + })?; - let parsed = colima::parse_tabasco_request(&received_bytes)?; + let parsed = colima::parse_tabasco_request(&received_bytes) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg1 failed to parse_tabasco_request:{:?}", err); + err + })?; if !parsed.has_msg1() { return Err(TabascoError::MissingFieldError("msg1")); } @@ -94,27 +122,59 @@ pub fn msg1(body_string: String) -> TabascoResponder { rng.fill(&mut pubkey_challenge); let sgx_ecc_handle = SgxEccHandle::new(); - sgx_ecc_handle.open()?; + sgx_ecc_handle.open() + .map_err(|err| { + println!("tabasco::attestation::sgx::msg1 failed to open sgx_ecc_handle:{:?}", err); + err + })?; let (private_key, public_key) = { - let mut ac_hash = ATTESTATION_CONTEXT.lock()?; + let mut ac_hash = ATTESTATION_CONTEXT.lock() + .map_err(|err| { + println!("tabasco::attestation::sgx::msg1 failed to obtain lock on ATTESTATION_CONTEXT:{:?}", err); + err + })?; let attestation_context = ac_hash .get_mut(&device_id) - .ok_or(TabascoError::NoDeviceError(device_id))?; + .ok_or(TabascoError::NoDeviceError(device_id)) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg1 NoDeviceError:{:?}", err); + err + })?; ( attestation_context.private_key.clone(), attestation_context.public_key.clone(), ) }; let dh_key = sgx_ecc_handle.compute_shared_dhkey(&private_key, &msg1.g_a)?; - sgx_ecc_handle.close()?; - let (smk, vk) = generate_sgx_symmetric_keys(&dh_key)?; - let msg2 = proc_msg1(&msg1, &smk, &private_key, &public_key)?; + sgx_ecc_handle.close() + .map_err(|err| { + println!("tabasco::attestation::sgx::msg1 failed to close sgx_ecc_handle:{:?}", err); + err + })?; + let (smk, vk) = generate_sgx_symmetric_keys(&dh_key) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg1 generate_sgx_symmetric_keys failed:{:?}", err); + err + })?; + let msg2 = proc_msg1(&msg1, &smk, &private_key, &public_key) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg1 proc_msg1 failed:{:?}", err); + err + })?; { - let mut ac_hash = ATTESTATION_CONTEXT.lock()?; + let mut ac_hash = ATTESTATION_CONTEXT.lock() + .map_err(|err| { + println!("tabasco::attestation::sgx::msg1 failed to obtain lock on ATTESTATION_CONTEXT:{:?}", err); + err + })?; let mut attestation_context = ac_hash .get_mut(&device_id) - .ok_or(TabascoError::NoDeviceError(device_id))?; + .ok_or(TabascoError::NoDeviceError(device_id)) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg1 NoDeviceError:{:?}", err); + err + })?; attestation_context.smk = Some(smk); attestation_context.vk = Some(vk); @@ -126,63 +186,119 @@ pub fn msg1(body_string: String) -> TabascoResponder { }; let serialized_challenge = - colima::serialize_sgx_attestation_challenge(context, &msg2, &pubkey_challenge)?; + colima::serialize_sgx_attestation_challenge(context, &msg2, &pubkey_challenge) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg1 serialize_sgx_attestation_challenge failed:{:?}", err); + err + })?; Ok(base64::encode(&serialized_challenge)) } pub fn msg3(body_string: String) -> TabascoResponder { - let received_bytes = base64::decode(&body_string)?; + let received_bytes = base64::decode(&body_string) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 base64 decode ov body_string failed:{:?}", err); + err + })?; - let parsed = colima::parse_tabasco_request(&received_bytes)?; + let parsed = colima::parse_tabasco_request(&received_bytes) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 parse_tabasco_request failed:{:?}", err); + err + })?; if !parsed.has_sgx_attestation_tokens() { println!("received data is incorrect. TODO: Handle this"); return Err(TabascoError::NoSGXAttestationTokenError); } let (msg3, msg3_quote, msg3_sig, pubkey_quote, pubkey_sig, device_id) = - colima::parse_attestation_tokens(&parsed)?; + colima::parse_attestation_tokens(&parsed) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 parse_attestation_tokens failed:{:?}", err); + err + })?; { let attestation_context = { - let ac_hash = ATTESTATION_CONTEXT.lock()?; + let ac_hash = ATTESTATION_CONTEXT.lock() + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 failed to obtain lock on ATTESTATION_CONTEXT:{:?}", err); + err + })?; let context = ac_hash .get(&device_id) - .ok_or(TabascoError::NoDeviceError(device_id))?; + .ok_or(TabascoError::NoDeviceError(device_id)) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 NoDeviceError:{:?}", err); + err + })?; (*context).clone() }; let expected_enclave_hash = { - let connection = crate::orm::establish_connection()?; + let connection = crate::orm::establish_connection() + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 establish_connection failed:{:?}", err); + err + })?; crate::orm::get_firmware_version_hash( &connection, &"sgx".to_string(), &attestation_context.firmware_version, - )? - .ok_or(TabascoError::MissingFieldError("firmware version"))? + ) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 get_firmware_version_hash failed:{:?}", err); + err + })? + .ok_or(TabascoError::MissingFieldError("firmware version")) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 MissingFieldError:{:?}", err); + err + })? }; let msg3_epid_pseudonym = authenticate_msg3( &attestation_context .msg1 - .ok_or(TabascoError::MissingFieldError("attestation_context.msg1"))?, + .ok_or(TabascoError::MissingFieldError("attestation_context.msg1")) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 MissingFieldError:{:?}", err); + err + })?, &attestation_context .msg2 - .ok_or(TabascoError::MissingFieldError("attestation_context.msg2"))?, + .ok_or(TabascoError::MissingFieldError("attestation_context.msg2")) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 MissingFieldError:{:?}", err); + err + })?, &msg3, &msg3_quote, &msg3_sig, &attestation_context .smk - .ok_or(TabascoError::MissingFieldError("attestation_context.smk"))?, + .ok_or(TabascoError::MissingFieldError("attestation_context.smk")) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 MissingFieldError:{:?}", err); + err + })?, &attestation_context .vk - .ok_or(TabascoError::MissingFieldError("attestation_context.vk"))?, + .ok_or(TabascoError::MissingFieldError("attestation_context.vk")) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 MissingFieldError:{:?}", err); + err + })?, &expected_enclave_hash, )?; let (pubkey_epid_pseudonym, pubkey_hash, enclave_name) = authenticate_pubkey_quote( &attestation_context .pubkey_challenge - .ok_or(TabascoError::MissingFieldError("pubkey_challenge"))?, + .ok_or(TabascoError::MissingFieldError("pubkey_challenge")) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 MissingFieldError:{:?}", err); + err + })?, &pubkey_quote, &pubkey_sig, )?; @@ -210,8 +326,16 @@ pub fn msg3(body_string: String) -> TabascoResponder { }); } - let connection = crate::orm::establish_connection()?; - crate::orm::update_or_create_device(&connection, device_id, &pubkey_hash, enclave_name)?; + let connection = crate::orm::establish_connection() + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 establish_connection failed:{:?}", err); + err + })?; + crate::orm::update_or_create_device(&connection, device_id, &pubkey_hash, enclave_name) + .map_err(|err| { + println!("tabasco::attestation::sgx::msg3 update_or_create_device failed:{:?}", err); + err + })? } // clean up the Attestation Context by removing this context diff --git a/tabasco/src/error.rs b/tabasco/src/error.rs index 42af54b6a..f993f44df 100644 --- a/tabasco/src/error.rs +++ b/tabasco/src/error.rs @@ -74,6 +74,10 @@ pub enum TabascoError { UnsupportedRequestError, #[error(display = "Tabasco: Direct message {}.", _0)] DirectMessageError(String, StatusCode), + #[error(display = "Tabasco: cbor error {}.", _0)] + CborError(String), + #[error(display = "Tabasco: Mutex error {}.", _0)] + MutexError(String), } #[cfg(feature = "sgx")] diff --git a/tabasco/src/orm/mod.rs b/tabasco/src/orm/mod.rs index 616adef35..2fbd5ae75 100644 --- a/tabasco/src/orm/mod.rs +++ b/tabasco/src/orm/mod.rs @@ -91,8 +91,17 @@ pub fn get_firmware_version_hash<'a>( .filter(firmware_versions::protocol.eq(protocol)) .filter(firmware_versions::version_num.eq(version)) .select(firmware_versions::hash) - .load(conn)?; + .load(conn) + .map_err(|err| { + println!("tabasco::orm::get_firmware_version_hash failed to query table:{:?}", err); + err + })?; + + let hash_vec = hex::decode(hashes[0].to_owned()) + .map_err(|err| { + println!("tabasco::orm::get_firmware_version_hash failed to decode contents:{:?}", err); + err + })?; - let hash_vec = hex::decode(hashes[0].to_owned())?; Ok(Some(hash_vec)) } diff --git a/tabasco/src/server.rs b/tabasco/src/server.rs index c74a70b83..4f9366ad1 100644 --- a/tabasco/src/server.rs +++ b/tabasco/src/server.rs @@ -14,6 +14,16 @@ use crate::attestation; use crate::attestation::psa; #[cfg(feature = "sgx")] use crate::attestation::sgx; +#[cfg(feature = "nitro")] +use crate::attestation::nitro; + +use lazy_static::lazy_static; +use std::sync::atomic::{ AtomicBool, Ordering}; + +lazy_static! { + pub static ref DEBUG_MODE: AtomicBool = AtomicBool::new(false); +} + use crate::error::*; use actix_web::{dev::Server, middleware, web, App, HttpServer}; use psa_attestation::{ @@ -26,28 +36,46 @@ use std::{ffi::c_void, ptr::null}; async fn verify_iat(input_data: String) -> TabascoResponder { if input_data.is_empty() { + println!("tabasco::verify_iat input_data is empty"); return Err(TabascoError::MissingFieldError("tabasco::verify_iat data")); } - let proto_bytes = base64::decode(&input_data)?; - - let proto = colima::parse_tabasco_request(&proto_bytes)?; + let proto_bytes = base64::decode(&input_data) + .map_err(|err| { + println!("tabasco::verify_iat decode of input data failed:{:?}", err); + err + })?; + + let proto = colima::parse_tabasco_request(&proto_bytes) + .map_err(|err| { + println!("tabasco::verify_iat parse_tabasco_request failed:{:?}", err); + err + })?; if !proto.has_proxy_psa_attestation_token() { + println!("tabasco::verify_iat proto does not have proxy psa attestation token"); return Err(TabascoError::NoProxyPSAAttestationTokenError); } let (token, pubkey, device_id) = colima::parse_proxy_psa_attestation_token(proto.get_proxy_psa_attestation_token()); - let pubkey_hash = { - let conn = crate::orm::establish_connection()?; - crate::orm::query_device(&conn, device_id)? + let conn = crate::orm::establish_connection() + .map_err(|err| { + println!("tabasco::verify_iat orm::establish_connection failed:{:?}", err); + err + })?; + crate::orm::query_device(&conn, device_id) + .map_err(|err| { + println!("tabasco::verify_iat orm::query_device failed:{:?}", err); + err + })? }; // verify that the pubkey we received matches the hash we received // during native attestation let calculated_pubkey_hash = ring::digest::digest(&ring::digest::SHA256, pubkey.as_ref()); if calculated_pubkey_hash.as_ref().to_vec() != pubkey_hash { + println!("tabasco::verify_iat hashes didn't match"); return Err(TabascoError::MismatchError { variable: "Tabasco::server public key", received: calculated_pubkey_hash.as_ref().to_vec(), @@ -67,6 +95,7 @@ async fn verify_iat(input_data: String) -> TabascoResponder { ) }; if lpk_ret != 0 { + println!("tabasco::verify_iat t_cose_sign1_verify_load_public_key failed:{:?}", lpk_ret); return Err(TabascoError::UnsafeCallError( "tabasco::server::verify_iat t_cose_sign1_verify_load_public_key", lpk_ret, @@ -101,6 +130,7 @@ async fn verify_iat(input_data: String) -> TabascoResponder { ) }; if sv_ret != 0 { + println!("tabasco::verify_iat sv_ret != 0"); return Err(TabascoError::UnsafeCallError( "tabasco::server::verify_iat t_cose_sign1_verify", sv_ret, @@ -115,6 +145,7 @@ async fn verify_iat(input_data: String) -> TabascoResponder { } if payload.ptr == null() { + println!("tabasco::verify_iat payload.ptr is null"); return Err(TabascoError::MissingFieldError("payload.ptr")); } @@ -148,7 +179,26 @@ async fn psa_router(psa_request: web::Path, input_data: String) -> Tabas Err(TabascoError::UnimplementedRequestError) } -pub fn server(url: String) -> Result { +#[allow(unused)] +async fn nitro_router(nitro_request: web::Path, input_data: String) -> TabascoResponder { + #[cfg(feature = "nitro")] + { + let inner = nitro_request.into_inner(); + if inner.as_str() == "AttestationToken" { + nitro::attestation_token(input_data) + } else { + println!("Tabasco::nitro_router returning unsupported with into_inner:{:?}", inner.as_str()); + Err(TabascoError::UnsupportedRequestError) + } + } + #[cfg(not(feature = "nitro"))] + Err(TabascoError::UnimplementedRequestError) +} + +pub fn server(url: String, debug: bool) -> Result { + if debug { + DEBUG_MODE.store(true, Ordering::SeqCst); + } let server = HttpServer::new(move || { App::new() .wrap(middleware::Logger::default()) @@ -156,6 +206,7 @@ pub fn server(url: String) -> Result { .route("/Start", web::post().to(attestation::start)) .route("/SGX/{sgx_request}", web::post().to(sgx_router)) .route("/PSA/{psa_request}", web::post().to(psa_router)) + .route("/Nitro/{nitro_request}", web::post().to(nitro_router)) }) .bind(&url) .map_err(|err| format!("binding error: {:?}", err))? diff --git a/test-collateral/generate_policy.py b/test-collateral/generate_policy.py index ca1378f4f..c795adc50 100755 --- a/test-collateral/generate_policy.py +++ b/test-collateral/generate_policy.py @@ -15,14 +15,25 @@ import argparse import subprocess import datetime +import os.path +from os import path identity_template = '\n\t\t{"certificate": "",\n\t\t"id": ,\n\t\t"roles": []}' -def get_enclave_hash(template): - subprocess.call(['dd', 'skip=960', 'count=32', 'if=css.bin', 'of=hash.bin', 'bs=1']); - hash_hex = subprocess.check_output(['xxd', '-ps', '-cols', '32', 'hash.bin']); - hash_hex = hash_hex.replace('\n', ''); - return template.replace('', hash_hex); +def get_enclave_hashes(template): + field_string = '' + if path.exists('css.bin'): + subprocess.call(['dd', 'skip=960', 'count=32', 'if=css.bin', 'of=hash.bin', 'bs=1']) + hash_hex = subprocess.check_output(['xxd', '-ps', '-cols', '32', 'hash.bin']) + hash_hex = hash_hex.replace('\n', '') + field_string = field_string + ' "mexico_city_hash_sgx": "' + hash_hex + '",' + # right now, we are totally faking TrustZone Hashes, so we're making it match SGX + field_string = field_string + '\n "mexico_city_hash_tz": "' + hash_hex + '",' + if path.exists('../mexico-city/PCR0'): + pcr0 = open('../mexico-city/PCR0').read().replace("\n", "") + field_string = field_string + '\n "mexico_city_hash_nitro": "' + pcr0 + '",' + + return template.replace('', field_string) def get_pi_hash(pi_file, template): hash_result = subprocess.check_output(['sha256sum', pi_file]) @@ -122,7 +133,7 @@ def streaming_order(args): # debug info policy = policy.replace('', str(args.debug_flag).lower()) -policy = get_enclave_hash(policy) +policy = get_enclave_hashes(policy) policy = get_pi_hash(args.pi_binary, policy) # execution strategy diff --git a/test-collateral/policy.template b/test-collateral/policy.template index 4db7341ad..7d9f41189 100644 --- a/test-collateral/policy.template +++ b/test-collateral/policy.template @@ -4,7 +4,7 @@ "sinaloa_url": "", "enclave_cert_expiry": { "year": , "month": , "day": , "hour":, "minute": }, "ciphersuite": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "mexico_city_hash": "", + "tabasco_url": "", "data_provision_order": , "streaming_order": , diff --git a/veracruz-test/Cargo.toml b/veracruz-test/Cargo.toml index 076be5e85..9a890eb5c 100644 --- a/veracruz-test/Cargo.toml +++ b/veracruz-test/Cargo.toml @@ -9,6 +9,7 @@ description = "Veracruz integration test. This is a full system test that shoul [features] sgx = ["durango/sgx","sinaloa/sgx", "sgx_types", "sgx_ucrypto", "sgx_alloc", "veracruz-utils/std", "tabasco/sgx", "colima/sgx_attestation"] tz = ["durango/tz","sinaloa/tz", "veracruz-utils/tz", "tabasco/psa", "colima/tz"] +nitro = ["durango/nitro", "sinaloa/nitro", "veracruz-utils/nitro", "tabasco/nitro"] [dependencies] durango = {path = "../durango"} diff --git a/veracruz-test/src/main.rs b/veracruz-test/src/main.rs index 6440a970b..61ef8cf25 100644 --- a/veracruz-test/src/main.rs +++ b/veracruz-test/src/main.rs @@ -69,7 +69,7 @@ mod tests { use sinaloa; use std::{io::Read, sync::Once}; use tabasco; - use veracruz_utils; + use veracruz_utils::{EnclavePlatform, policy, VeracruzPolicy}; #[derive(Debug, Error)] pub enum VeracruzTestError { @@ -80,7 +80,7 @@ mod tests { #[error(display = "VeracruzTest: DurangoError: {:?}.", _0)] DurangoError(#[error(source)] durango::DurangoError), #[error(display = "VeracruzTest: VeracruzUtilError: {:?}.", _0)] - VeracruzUtilError(#[error(source)] veracruz_utils::policy::VeracruzUtilError), + VeracruzUtilError(#[error(source)] policy::VeracruzUtilError), #[error(display = "VeracruzTest: SinaloaError: {:?}.", _0)] SinaloaError(#[error(source)] sinaloa::SinaloaError), #[error(display = "VeracruzTest: ColimaError: {:?}.", _0)] @@ -98,7 +98,7 @@ mod tests { env_logger::builder().init(); let _main_loop_handle = std::thread::spawn(|| { let mut sys = System::new("Tabasco Server"); - let server = tabasco::server::server(tabasco_url).unwrap(); + let server = tabasco::server::server(tabasco_url, false).unwrap(); sys.block_on(server).unwrap(); }); }); @@ -287,18 +287,26 @@ mod tests { #[actix_rt::test] async fn veracruz_phase4_linear_regression_two_clients_parallel() { let policy_json = std::fs::read_to_string(LINEAR_REGRESSION_PARALLEL_POLICY).unwrap(); - let policy = veracruz_utils::VeracruzPolicy::from_json(&policy_json).unwrap(); + let policy = VeracruzPolicy::from_json(&policy_json).unwrap(); setup(policy.tabasco_url().clone()); task::sleep(std::time::Duration::from_millis(5000)).await; let server_handle = server_tls_loop(LINEAR_REGRESSION_PARALLEL_POLICY); + #[cfg(feature = "sgx")] + let target_platform = EnclavePlatform::SGX; + #[cfg(feature = "tz")] + let target_platform = EnclavePlatform::TrustZone; + #[cfg(feature = "nitro")] + let target_platform = EnclavePlatform::Nitro; + let program_provider_handle = async { task::sleep(std::time::Duration::from_millis(10000)).await; - let mut client = - durango::Durango::new(PROGRAM_CLIENT_CERT, PROGRAM_CLIENT_KEY, &policy_json)?; + durango::Durango::new(PROGRAM_CLIENT_CERT, PROGRAM_CLIENT_KEY, + &policy_json, + &target_platform)?; let program_filename = LINEAR_REGRESSION_WASM; let program_data = read_binary_file(&program_filename)?; client.send_program(&program_data)?; @@ -307,7 +315,7 @@ mod tests { let data_provider_handle = async { task::sleep(std::time::Duration::from_millis(11000)).await; let mut client = - durango::Durango::new(DATA_CLIENT_CERT, DATA_CLIENT_KEY, &policy_json)?; + durango::Durango::new(DATA_CLIENT_CERT, DATA_CLIENT_KEY, &policy_json, &target_platform)?; let data_filename = LINEAR_REGRESSION_DATA; let data = read_binary_file(&data_filename)?; @@ -341,7 +349,7 @@ mod tests { result_retrievers: &[usize], ) -> Result<(), VeracruzTestError> { let policy_json = std::fs::read_to_string(policy_path)?; - let policy = veracruz_utils::VeracruzPolicy::from_json(&policy_json)?; + let policy = VeracruzPolicy::from_json(&policy_json)?; setup(policy.tabasco_url().clone()); info!("### Step 0. Read the policy file {}.", policy_path); @@ -357,7 +365,14 @@ mod tests { info!("### Step 2. Set up all durango sessions."); let mut clients = Vec::new(); for (cert, key) in client_configs.iter() { - clients.push(durango::Durango::new(cert, key, &policy_json)?); + #[cfg(feature = "sgx")] + let target_platform = EnclavePlatform::SGX; + #[cfg(feature = "tz")] + let target_platform = EnclavePlatform::TrustZone; + #[cfg(feature = "nitro")] + let target_platform = EnclavePlatform::Nitro; + + clients.push(durango::Durango::new(cert, key, &policy_json, &target_platform)?); } info!( diff --git a/veracruz-utils/Cargo.toml b/veracruz-utils/Cargo.toml index 84e4b5c8e..6b6b3cbb0 100644 --- a/veracruz-utils/Cargo.toml +++ b/veracruz-utils/Cargo.toml @@ -10,6 +10,7 @@ description = "Miscellaneous and common code used by multiple Veracruz component sgx = ["sgx_tstd", "serde/mesalock_sgx", "serde_json/mesalock_sgx", "serde_json/alloc"] tz = ["serde_json/std"] std = ["x509-parser", "serde/std", "rustls","serde_json/std", "actix-web", "futures", "actix-http", "failure"] +nitro = ["serde_json/std", "nix", "byteorder"] [dependencies] sgx_tstd = { rev = "v1.1.2", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } @@ -17,12 +18,14 @@ serde = { git = "https://github.com/veracruz-project/serde.git", features=["deri x509-parser={ version = "0.7.0", optional = true } serde_json = { git = "https://github.com/veracruz-project/json.git", branch = "veracruz", default-features = false } # The cargo patch mechanism does NOT work when we add function into a macro_rules! -rustls = { git = "https://github.com/veracruz-project/rustls.git", branch = "self_signed", optional = true } +rustls = { git = "https://github.com/veracruz-project/rustls.git", branch = "veracruz", optional = true } actix-web = { version = "2.0.0", optional = true } futures = { version = "0.3", optional = true } actix-http = { version = "1.0", optional = true } failure = { version = "0.1", optional = true } err-derive = "0.2" +nix = { version = "0.15", optional = true } +byteorder = { version = "1.3", optional = true } [build-dependencies] uuid = { version = "0.7", features = ["v4"] } diff --git a/veracruz-utils/src/lib.rs b/veracruz-utils/src/lib.rs index 4d2db8f17..50ff96d1b 100644 --- a/veracruz-utils/src/lib.rs +++ b/veracruz-utils/src/lib.rs @@ -28,3 +28,17 @@ pub use crate::mexico_city_opcode::*; pub mod jalisco_opcode; #[cfg(feature = "tz")] pub use crate::jalisco_opcode::*; + +#[cfg(feature = "nitro")] +pub mod nitro; +#[cfg(feature = "nitro")] +pub use crate::nitro::*; + +#[cfg(feature = "nitro")] +pub mod vsocket; +#[cfg(feature = "nitro")] +pub use self::vsocket::*; +#[cfg(feature = "nitro")] +pub mod nitro_enclave; +#[cfg(feature = "nitro")] +pub use self::nitro_enclave::*; diff --git a/veracruz-utils/src/nitro.rs b/veracruz-utils/src/nitro.rs new file mode 100644 index 000000000..1ebe7b8a7 --- /dev/null +++ b/veracruz-utils/src/nitro.rs @@ -0,0 +1,266 @@ +//! Structs needed for AWS Nitro Enclaves, both inside and outside of the +//! enclave +//! +//! ## Authors +//! +//! The Veracruz Development Team. +//! +//! ## Licensing and copyright notice +//! +//! See the `LICENSE.markdown` file in the Veracruz root directory for +//! information on licensing and copyright. + +use serde::{Deserialize, Serialize}; + +use byteorder::{ByteOrder, LittleEndian}; +use err_derive::Error; +use nix::errno::Errno::EINTR; +use nix::sys::socket::{recv, send, MsgFlags}; +use std::os::unix::io::RawFd; + +/// The Status value returned by the Nitro enclave for operations +/// This is intended to be received as a bincode serialized +/// `NitroRootEnclaveMessage::Status` +#[derive(Serialize, Deserialize, Debug)] +pub enum NitroStatus { + /// The operation generating the message succeeded + Success, + /// The operation generating the message failed + Fail, + /// The requested operation is not yet implemented + Unimplemented, +} + +/// An enumerated type describing messages passed between Mexico City +/// and the Nitro Root Enclave +/// These messages are inteded to be serialized using bincode before transport, +/// and deserialized using bincode after transport +#[derive(Serialize, Deserialize, Debug)] +pub enum NitroRootEnclaveMessage { + /// A message generated by an operation that did not return data, but did + /// return a status. + /// Most operations return data, but if they fail, they will return a + /// status set to `NitroStatus::Fail` (or `NitroStatus::Unimplemented` if + /// it is not implmeneted). + /// Parameters: + /// NitroStatus - the Status + Status(NitroStatus), + /// A request to fetch the firmware version from the Nitro Root Enclave + FetchFirmwareVersion, + /// A response to the `FetchFirmwareVersion` message, it contains the + /// firmware version of the Nitro Root Enclave, as a string + FirmwareVersion(String), + /// A request to set the mexico city hash that the Nitro Root Enclave + /// expects during the proxy attestation. This is a dirty hack to allow + /// TrustZone to work (as we are faking attestation) + /// The Vec input is the hash value + SetMexicoCityHashHack(Vec), + /// A request to start the native attestation process. + /// This is usually initiated from the Proxy Attestation Service + /// The values: + /// Vec - The 128-bit challenge value generated by the caller + /// i32 - A device ID set by the caller. Will be used by the enclave + /// in future operations + NativeAttestation(Vec, i32), + /// A response to the NativeAttestation message. This is generated by the + /// enclave. + /// The parameters: + /// Vec - The native attestation token generated by the enclave + /// Vec - The public key, randomly generated by the enclave, to be used + /// as the device-specific proxy public key + TokenData(Vec, Vec), + /// A request (usually initiated by the Mexico City enclave) to start the + /// proxy attestation process. + /// The parameters: + /// Vec - The 128-bit challenge value generated by the caller + /// Vec - The native token value, generated by the caller. This is also, + /// referred to as the local attestation token on some platforms + /// String - The (supposedly) randomly generated enclave name + ProxyAttestation(Vec, Vec, String), + /// A response to the ProxyAttestation message. This is the PSA token + /// information that will be sent to the Proxy Attestation Service + /// The parameters: + /// Vec - The PSA Attestation token to be forwarded to the Proxy + /// Attestation Service + /// Vec - The public key + /// u32 - The device ID that identifies the Nitro Root Enclave to the + /// Proxy Attestation Service + PSAToken(Vec, Vec, u32), +} + +/// An enumerated type describing messages passed between to/from the Mexico +/// City enclave (These originate from the Untrusted Pass-through (Sinaloa) +/// These messages are inteded to be serialized using bincode before transport, +/// and deserialized using bincode after transport +#[derive(Serialize, Deserialize, Debug)] +pub enum MCMessage { + // A message generated by an operation that did not return data, but did + /// return a status. + /// Most operations return data, but if they fail, they will return a + /// status set to `NitroStatus::Fail` (or `NitroStatus::Unimplemented` if + /// it is not implmeneted). + /// Parameters: + /// NitroStatus - the Status + Status(NitroStatus), + /// A request to initialize the Mexico City enclave with the provided + /// policy + /// parameters: + /// String - The policy, in JSON format + Initialize(String), // policy_json + /// A request for the self-signed certificate generated by the enclave. + /// This message is necessary for allowing tests to operate without + /// performing the full attestation flow. + /// This command should not be used in real environments. It does not + /// constitute a security risk in and of itself, but the fetched value + /// cannot be trusted, and thus the certificate should actually be + /// retrieved using attestation. + /// This request may be removed in the future. + GetEnclaveCert, + /// The response to the `GetEnclaveCert` message. It contains the + /// self-signed certificate generated by the enclave + /// Parameters: + /// Vec - the DER encoded certificate + EnclaveCert(Vec), + /// A request for the randomly generated name of the enclave. + /// This message is necessary for allowing tests to operate without + /// performing the full attestation flow. + /// This command should not be used in real environments. It does not + /// constitute a security risk in and of itself, but the fetched value + /// cannot be trusted, and thus the name should actually be retrieved using + /// attestation. + /// This request may be removed in the future. + GetEnclaveName, + /// The response to the `GetEnclaveName` message. It contains the randomly + /// generated name of the enclave + /// Parameters: + /// String - the name of the enclave + EnclaveName(String), + /// A request for a PSA Attestation (proxy) token from the Mexico City + /// enclave. + /// Parameters: + /// Vec - The 128-bit randomly generated challenge value + GetPSAAttestationToken(Vec), + /// The response to the `GetPSAAttestationToken` request. + /// Parameters: + /// Vec - The PSA Attestation token + /// Vec - The public key that was randomly generated by the enclave + /// to be used for the TLS channel communications + /// i32 - The Device ID that he Nitro Root Enclave uses to identify + /// itself to the Proxy Attestation Service + PSAAttestationToken(Vec, Vec, i32), + /// A request to establish a new TLS session with the enclave + NewTLSSession, + /// The response to the `NewTLSSession` message + /// Parameters: + /// u32 - The Session ID of the created TLS Session + TLSSession(u32), + /// A request to close an already established TLS session + /// Parameters: + /// u32 - The Session ID of the session to be closed + CloseTLSSession(u32), + /// Request to determine if the TLS Session needs data to be sent to it + /// Parameters: + /// u32 - The Session ID of the TLS session + GetTLSDataNeeded(u32), + /// Response to `GetTLSDataNeeded` message + /// Parameters: + /// bool - is data needed? + TLSDataNeeded(bool), + /// Request to send TLS data to the enclave + /// Parameters: + /// u32 - the Session ID of the TLS Session associated with the data + /// Vec - The TLS Data + SendTLSData(u32, Vec), + /// Request TLS Data from the enclave + /// Parameters: + /// u32 - the Session ID of the TLS Session to request data from + GetTLSData(u32), + /// Response to `GetTLSData` + /// Parameters: + /// Vec - The TLS Data. May be empty + /// bool - a flag indicating if the TLS session is still alive + TLSData(Vec, bool), // TLS Data, alive_flag + /// A request to reset the enclave + ResetEnclave, +} + +/// a enumerated type for Veracruz-specific socket errors +#[derive(Debug, Error)] +pub enum VeracruzSocketError { + /// An error was returned by nix + #[error(display = "VeracruzSocketError: Nix Error: {:?}", _0)] + NixError(#[error(source)] nix::Error), +} + +/// Send a buffer of data (using a length, buffer protocol) to the file +/// descriptor `fd` +pub fn send_buffer(fd: RawFd, buffer: &Vec) -> Result<(), VeracruzSocketError> { + let len = buffer.len(); + // first, send the length of the buffer + { + let mut buf = [0u8; 9]; + LittleEndian::write_u64(&mut buf, buffer.len() as u64); + let mut sent_bytes = 0; + while sent_bytes < buf.len() { + sent_bytes += match send(fd, &buf[sent_bytes..buf.len()], MsgFlags::empty()) { + Ok(size) => size, + Err(err) => { + return Err(VeracruzSocketError::NixError(err)); + } + }; + } + } + // next, send the buffer + { + let mut sent_bytes = 0; + while sent_bytes < len { + let size = match send(fd, &buffer[sent_bytes..len], MsgFlags::empty()) { + Ok(size) => size, + Err(nix::Error::Sys(_)) => 0, + Err(err) => { + return Err(VeracruzSocketError::NixError(err)); + } + }; + sent_bytes += size; + } + } + return Ok(()); +} + +/// Read a buffer of data (using a length, buffer protocol) from the file +/// descriptor `fd` +pub fn receive_buffer(fd: RawFd) -> Result, VeracruzSocketError> { + // first, read the length + let length = { + let mut buf = [0u8; 9]; + let len = buf.len(); + let mut received_bytes = 0; + while received_bytes < len { + received_bytes += match recv(fd, &mut buf[received_bytes..len], MsgFlags::empty()) { + Ok(size) => size, + Err(nix::Error::Sys(EINTR)) => 0, + Err(err) => { + println!("I have experienced an error:{:?}", err); + return Err(VeracruzSocketError::NixError(err)); + } + } + } + LittleEndian::read_u64(&buf) as usize + }; + let mut buffer: Vec = vec![0; length]; + // next, read the buffer + { + let mut received_bytes: usize = 0; + while received_bytes < length { + received_bytes += match recv(fd, &mut buffer[received_bytes..length], MsgFlags::empty()) + { + Ok(size) => size, + Err(nix::Error::Sys(EINTR)) => 0, + Err(err) => { + return Err(VeracruzSocketError::NixError(err)); + } + } + } + } + return Ok(buffer); +} diff --git a/veracruz-utils/src/nitro_enclave.rs b/veracruz-utils/src/nitro_enclave.rs new file mode 100644 index 000000000..eb405de3b --- /dev/null +++ b/veracruz-utils/src/nitro_enclave.rs @@ -0,0 +1,334 @@ +//! Nitro-Enclave-specific material for Veracruz +//! +//! ## Authors +//! +//! The Veracruz Development Team. +//! +//! ## Licensing and copyright notice +//! +//! See the `LICENSE.markdown` file in the Veracruz root directory for +//! information on licensing and copyright. + +use err_derive::Error; +use nix::sys::socket::sockopt::{ReuseAddr, ReusePort}; +use nix::sys::socket::{ + accept, bind, listen, setsockopt, shutdown, socket, AddressFamily, Shutdown, SockAddr, + SockFlag, SockType, +}; +use nix::unistd::close; +use serde_json::Value; +use std::os::unix::io::AsRawFd; +use std::process::Command; +use std::thread::JoinHandle; + +/// Errors generated by Nitro enclave components of Veracruz +#[derive(Debug, Error)] +pub enum NitroError { + /// An error occurred while serializing or deserializing + #[error(display = "Nitro: Serde Error")] + SerdeError, + /// A JSON-specific error occured while serializing or deserializing + #[error(display = "nitro: Serde JSON Error:{:?}", _0)] + SerdeJsonError(#[error(source)] serde_json::error::Error), + /// An error was generated from the nix crate + #[error(display = "Nitrno: Nix Error:{:?}", _0)] + NixError(#[error(source)] nix::Error), + /// an IO error occcurred + #[error(display = "Nitro: IO Error:{:?}", _0)] + IOError(#[error(source)] std::io::Error), + /// An error was generated by the AWS Nitro CLI + #[error(display = "Nitro: CLI error")] + CLIError, + /// a Veracruz-specific socket error occurred + #[error(display = "Nitro: Veracruz Socket Error:{:?}", _0)] + VeracruzSocketError(#[error(source)] crate::VeracruzSocketError), + /// An error occured while processing UTF8 string data + #[error(display = "Nitro: Utf8Error:{:?}", _0)] + Utf8Error(#[error(source)] std::str::Utf8Error), + /// an error occured while working with an EC2 instance + #[error(display = "Nitro: EC2 Error")] + EC2Error, + /// A mutex error occurred + #[error(display = "nitro: Mutex Error")] + MutexError, +} + +/// a struct for holding all of the information about a nitro enclave +pub struct NitroEnclave { + /// The enclave ID, as generated from the Nitro CLI tool when the enclave + /// is created - it's the EC2-instance ID appended with an enclave-specific + /// value + enclave_id: String, + /// A convenience struct for handling VSOCK connections to the enclave + vsocksocket: crate::vsocket::VsockSocket, + /// The thread that will handle ocall operations from the enclave, if enabled + /// (hence it's an `Option` + ocall_thread: Option>, + /// A communication channel to tell the ocall thread to terminate (if enabled) + ocall_terminate_sender: Option>>, + /// the path to the Nictro CLI function. Not all AMI images have it in the + /// same place in the file system, so we need to keep track of it + nitro_cli_path: String, +} + +/// The port that is used to communicate with the enclave +const VERACRUZ_PORT: u32 = 5005; +/// A convenience value to be use when you want to listen to any CID for connections +const VMADDR_CID_ANY: u32 = 0xFFFFFFFF; +/// The port that the ocall thread will be waiting for socket connections on +const OCALL_PORT: u32 = 5006; +/// the maximum back log of events that will be maintained while listening to +/// a socket +const BACKLOG: usize = 128; + +/// A type declaration for the function the ocall thread will call when it receives +/// data from the enclave +pub type OCallHandler = fn(Vec) -> Result, NitroError>; + +impl NitroEnclave { + /// create a new Nitro enclave, started with the file in eif_path + /// If `ocall_handler` is not `None`, an ocall listening thread will be started + /// and will call the function in `ocall_handler` when it recieves data from + /// the enclave + pub fn new( + nitro_sbin: bool, + eif_path: &str, + debug: bool, + ocall_handler: Option, + ) -> Result { + let mut args = vec![ + "run-enclave", + "--eif-path", + eif_path, + "--cpu-count", + "2", + "--memory", + "256", + ]; + if debug { + args.push("--debug-mode=true"); + } + let nitro_cli_path = { + match nitro_sbin { + true => "/usr/sbin/nitro-cli", + false => "/usr/bin/nitro-cli", + } + }; + let stdout = loop { + let enclave_result = Command::new(nitro_cli_path) + .args(&args) + .output() + .map_err(|err| err); + match enclave_result { + Err(err) => { + println!("NitroEnclave::new failed to start enclave:{:?}", err); + println!("sleeping before trying again"); + std::thread::sleep(std::time::Duration::from_millis(1000)); + continue; + } + Ok(result) => { + if !result.status.success() { + let enclave_result_stderr = std::str::from_utf8(&result.stderr)?; + println!("NitroEnclave::new CLI error:{:?}", enclave_result_stderr); + println!("sleeping before trying again"); + std::thread::sleep(std::time::Duration::from_millis(1000)); + continue; + } else { + break result.stdout; + } + } + } + }; + + let enclave_result_stdout = std::str::from_utf8(&stdout)?; + println!("enclave_result_stdout:{:?}", enclave_result_stdout); + + let enclave_data: Value = serde_json::from_str(enclave_result_stdout)?; + let cid: u32 = if !enclave_data["EnclaveCID"].is_number() { + return Err(NitroError::SerdeError); + } else { + serde_json::from_value(enclave_data["EnclaveCID"].clone()).unwrap() + }; + + let (ocall_thread_opt, sender) = match ocall_handler { + None => (None, None), // Do nothing, we don't need to support ocalls + Some(handler) => { + let (tx, rx): ( + std::sync::mpsc::Sender, + std::sync::mpsc::Receiver, + ) = std::sync::mpsc::channel(); + let ocall_thread = + std::thread::spawn(move || NitroEnclave::ocall_loop(handler, rx)); + (Some(ocall_thread), Some(std::sync::Mutex::new(tx))) + } + }; + + let enclave: Self = NitroEnclave { + enclave_id: enclave_data["EnclaveID"] + .to_string() + .trim_matches('"') + .to_string(), + vsocksocket: crate::vsocket::vsock_connect(cid, VERACRUZ_PORT)?, + ocall_thread: ocall_thread_opt, + ocall_terminate_sender: sender, + nitro_cli_path: nitro_cli_path.to_string(), + }; + return Ok(enclave); + } + + /// The function started in it's own thread when an `ocall_handler` is specified + /// This function cannot return, so `panic!`s are a necessary evil in here + fn ocall_loop(handler: OCallHandler, terminate_rx: std::sync::mpsc::Receiver) { + let socket_fd = socket( + AddressFamily::Vsock, + SockType::Stream, + SockFlag::SOCK_NONBLOCK, + None, + ) + .expect("NitroEnclave::ocall_loop failed to create a socket"); + + if let Err(err) = setsockopt(socket_fd, ReuseAddr, &true) { + println!( + "NitroEnclave::ocall_loop setsockopt failed for ReuseAddr({:?}). Terminating loop.", + err + ); + return; + } + if let Err(err) = setsockopt(socket_fd, ReusePort, &true) { + println!( + "NitroEnclave::ocall_loop setsockopt failed for ReusePort({:?}). Terminating loop.", + err + ); + return; + } + + let sockaddr = SockAddr::new_vsock(VMADDR_CID_ANY, OCALL_PORT); + + let mut im_done: bool = false; + + while let Err(err) = bind(socket_fd, &sockaddr) { + println!( + "NitroEnclave::ocall_loop failed to bind to socket:{:?}", + err + ); + if err == nix::Error::Sys(nix::errno::Errno::EADDRINUSE) { + // before we continue, check to see if we should terminate + if let Ok(terminate) = terminate_rx.try_recv() { + if terminate { + im_done = true; + break; + } + } + println!("sleeping before trying again"); + std::thread::sleep(std::time::Duration::from_millis(1000)); + } else { + panic!( + "NitroEnclave::ocall_loop received an unhandled error from bind:{:?}", + err + ); + } + } + + if !im_done { + listen(socket_fd, BACKLOG) + .map_err(|err| NitroError::NixError(err)) + .expect("NitroEnclave::ocall_loop listen failed"); + loop { + match accept(socket_fd) { + Ok(fd) => { + let received_buffer = crate::nitro::receive_buffer(fd) + .expect("NitroEnclave::ocall_loop failed to receive buffer"); + // call the handler + let return_buffer = handler(received_buffer) + .expect("NitroEnclave::ocall_loop handler failed"); + crate::nitro::send_buffer(fd, &return_buffer) + .expect("NitroEnclave::ocall_loop failed to send buffer"); + } + Err(err) => match err { + nix::Error::Sys(_) => { + if let Ok(terminate) = terminate_rx.try_recv() { + if terminate { + break; + } + } + } + _ => println!("NitroEnclave::ocall_loop received error:{:?}", err), + }, + } + } + } + + // TODO: Use veracruz-utils::VsockSocket, which should handle shutdown, close itself in it's Drop function + if let Err(err) = shutdown(socket_fd, Shutdown::Both) { + match err { + nix::Error::Sys(err) => { + match err { + nix::errno::Errno::ENOTCONN => (), // this is normal + _ => println!("NitroEnclave::ocall_loop failed to shutdown socket({:?}. This might cause you problems in the future.", err), + } + }, + _ => println!("NitroEnclave::ocall_loop failed to shutdown socket({:?}. This might cause you problems in the future.", err), + } + } + if let Err(err) = close(socket_fd) { + println!("NitroEnclave::ocall_loop failed to close socket file handle({:?}). This might cause you problems in the future", err); + } + } + + /// send a buffer of data to the enclave + pub fn send_buffer(&self, buffer: &Vec) -> Result<(), NitroError> { + crate::nitro::send_buffer(self.vsocksocket.as_raw_fd(), buffer) + .map_err(|err| NitroError::VeracruzSocketError(err)) + } + + /// receive a buffer of data from the enclave + pub fn receive_buffer(&self) -> Result, NitroError> { + crate::nitro::receive_buffer(self.vsocksocket.as_raw_fd()) + .map_err(|err| NitroError::VeracruzSocketError(err)) + } +} + +impl Drop for NitroEnclave { + /// Drop the enclave. In ideal conditions, this means that the enclave will + /// be terminated. + fn drop(&mut self) { + // first, tell the ocall loop to terminate + if let Some(tx_mutex) = &self.ocall_terminate_sender { + let sender_guard = tx_mutex.lock().unwrap(); + if let Err(err) = sender_guard.send(true) { + println!("NitroEnclave::drop failed to send terminate message to ocall_thread. You can't do anything about this, since we are in the drop method, but I thought you would want to know:{:?}", err); + } + } + + // second, wait for the ocall loop to terminate + // This is referred to as the "Option dance" - https://users.rust-lang.org/t/spawn-threads-and-join-in-destructor/1613 + // we can only do the "take" because we are effectively a destructor + if let Some(thread_handle) = self.ocall_thread.take() { + if let Err(err) = thread_handle.join() { + println!("NitroEnclave::drop failed to join to the ocall_thread. You can't do anything about this, since we are in the drop method, but I thought you would want to know:{:?}", err); + } + } + + // now, shutdown the enclave + loop { + let enclave_result = Command::new(&self.nitro_cli_path) + .args(&["terminate-enclave", "--enclave-id", &self.enclave_id]) + .output(); + match enclave_result { + Err(err) => { + println!("NitroEnclave::drop Command::new returned err:{:?}, sleeping and trying again", err); + std::thread::sleep(std::time::Duration::from_millis(1000)); + continue; + } + Ok(result) => { + if !result.status.success() { + println!("NitroEnclave::drop failed to terminate the enclave (exit_status:{:?}. You will need to terminate it yourself.", result.status); + let result_stderr = std::str::from_utf8(&result.stderr).unwrap(); + println!("NitroEnclave::drop CLI error:{:?}", result_stderr); + } + break; + } + } + } + } +} diff --git a/veracruz-utils/src/policy.rs b/veracruz-utils/src/policy.rs index d7730d0d1..68f608663 100644 --- a/veracruz-utils/src/policy.rs +++ b/veracruz-utils/src/policy.rs @@ -82,6 +82,8 @@ pub enum VeracruzUtilError { DataProviderError, #[error(display = "VeracruzUtil: Policy has no result retriever.")] NoResultRetrieverError, + #[error(display = "VeracruzUtil: Policy is missing a field: {:?}", _0)] + MissingPolicyFieldError(String), } #[cfg(feature = "std")] @@ -340,8 +342,12 @@ pub struct VeracruzPolicy { /// The ciphersuite that will be used with the TLS connections between the /// principals of the computation and the enclave. ciphersuite: String, - /// The hash of the Veracruz trusted runtime. - mexico_city_hash: String, + /// The hash of the Veracruz trusted runtime for SGX enclaves. + mexico_city_hash_sgx: Option, + /// The hash of the Veracruz trusted runtime for TrustZone TAs. + mexico_city_hash_tz: Option, + /// The hash of the Veracruz trusted runtime for AWS Nitro Enclaves. + mexico_city_hash_nitro: Option, /// The declared ordering of data inputs, provided by the various data /// providers, as specified in the policy. Note that data providers can /// provision their inputs asynchronously, and in an arbitrary order. Once @@ -369,6 +375,15 @@ pub struct VeracruzPolicy { streaming_order: Vec, } +/// an enumerated type representing the platform the enclave is running on +pub enum EnclavePlatform { + SGX, + TrustZone, + Nitro, + /// The Mock platform is for unit testing (durango unit tests, at the moment) + Mock, +} + impl VeracruzPolicy { /// Constructs a new Veracruz policy type, validating the well-formedness of /// the resulting policy in the process. Returns `Ok(policy)` iff these @@ -378,7 +393,9 @@ impl VeracruzPolicy { sinaloa_url: String, enclave_cert_expiry: VeracruzExpiry, ciphersuite: String, - mexico_city_hash: String, + mexico_city_hash_sgx: Option, + mexico_city_hash_tz: Option, + mexico_city_hash_nitro: Option, data_provision_order: Vec, streaming_order: Vec, tabasco_url: String, @@ -391,7 +408,9 @@ impl VeracruzPolicy { sinaloa_url, enclave_cert_expiry, ciphersuite, - mexico_city_hash, + mexico_city_hash_sgx, + mexico_city_hash_tz, + mexico_city_hash_nitro, data_provision_order, tabasco_url, pi_hash, @@ -443,8 +462,26 @@ impl VeracruzPolicy { /// Returns the hash of the trusted Veracruz runtime, associated with this /// policy. #[inline] - pub fn mexico_city_hash(&self) -> &String { - &self.mexico_city_hash + pub fn mexico_city_hash(&self, platform: &EnclavePlatform) -> Result<&String, VeracruzUtilError> { + let hash = match platform { + EnclavePlatform::SGX => match &self.mexico_city_hash_sgx { + Some(hash) => hash, + None => return Err(VeracruzUtilError::MissingPolicyFieldError("mexico_city_hash_sgx".to_string())), + }, + EnclavePlatform::TrustZone => match &self.mexico_city_hash_tz { + Some(hash) => hash, + None => return Err(VeracruzUtilError::MissingPolicyFieldError("mexico_city_hash_tz".to_string())), + }, + EnclavePlatform::Nitro => match &self.mexico_city_hash_nitro { + Some(hash) => hash, + None => return Err(VeracruzUtilError::MissingPolicyFieldError("mexico_city_hash_nitro".to_string())), + }, + EnclavePlatform::Mock => match &self.mexico_city_hash_sgx { + Some(hash) => hash, + None => return Err(VeracruzUtilError::MissingPolicyFieldError("mexico_city_hash_sgx".to_string())), + }, + }; + return Ok(&hash); } /// Returns the fixed data provisioning order, associated with this policy. diff --git a/veracruz-utils/src/vsocket.rs b/veracruz-utils/src/vsocket.rs new file mode 100644 index 000000000..ee94424a0 --- /dev/null +++ b/veracruz-utils/src/vsocket.rs @@ -0,0 +1,71 @@ +//! Virtual socket handler for Veracruz +//! +//! ## Authors +//! +//! The Veracruz Development Team. +//! +//! ## Licensing and copyright notice +//! +//! See the `LICENSE.markdown` file in the Veracruz root directory for +//! information on licensing and copyright. + +use nix::sys::socket::{connect, shutdown, socket}; +use nix::sys::socket::{AddressFamily, Shutdown, SockAddr, SockFlag, SockType}; +use nix::unistd::close; +use std::os::unix::io::{AsRawFd, RawFd}; + +/// Maximum number of connection attempts to make before erroring out +const MAX_CONNECTION_ATTEMPTS: usize = 5; + +/// A struct containing the details of a VSOCK +pub struct VsockSocket { + socket_fd: RawFd, +} + +impl VsockSocket { + /// create a new VsockSocket from a RawFd for a socket + fn new(socket_fd: RawFd) -> Self { + VsockSocket { socket_fd } + } +} + +impl Drop for VsockSocket { + /// Drop a socket by closing it. If it fails, report a message, but don't + /// do anything else (it could fail because it's not connected) + fn drop(&mut self) { + shutdown(self.socket_fd, Shutdown::Both).unwrap_or_else(|_| ()); // Do nothing on failure. It's non fatal and a warning message is just confusing + close(self.socket_fd).unwrap_or_else(|e| eprintln!("Failed to close socket: {:?}", e)); + } +} + +impl AsRawFd for VsockSocket { + /// Extract the raw RadFd from the VsockSocket + fn as_raw_fd(&self) -> RawFd { + self.socket_fd + } +} + +/// Initiate a connection on an AF_VSOCK socket +pub fn vsock_connect(cid: u32, port: u32) -> Result { + let sockaddr = SockAddr::new_vsock(cid, port); + let mut err: nix::Error = nix::Error::UnsupportedOperation; // just a placeholder + + for i in 0..MAX_CONNECTION_ATTEMPTS { + let vsocket = VsockSocket::new(socket( + AddressFamily::Vsock, + SockType::Stream, + SockFlag::empty(), + None, + )?); + match connect(vsocket.as_raw_fd(), &sockaddr) { + Ok(_) => return Ok(vsocket), + Err(e) => err = e, + } + + // Exponentially backoff before retrying to connect to the socket + std::thread::sleep(std::time::Duration::from_secs(1 << i)); + } + + // in case of success, this should never be reached + Err(err) +}