Skip to content
This repository was archived by the owner on Jul 16, 2020. It is now read-only.

Implement SGX encrypted channel demo #30

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
503 changes: 405 additions & 98 deletions intel-sgx/Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions intel-sgx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ members = ['attestation-enclave', 'attestation-daemon', 'attestation-tenant']
[patch.crates-io]
dcap-ql = { git = "https://github.com/lkatalin/rust-sgx", branch = "serde-pck", rev = "aa81839e714ad7501e6ef44b2d4e61c8574548d4" }
sgx-isa = { git = "https://github.com/lkatalin/rust-sgx", branch = "serde-pck", rev = "aa81839e714ad7501e6ef44b2d4e61c8574548d4" }
mbedtls = { git = "https://github.com/haraldh/rust-mbedtls", branch = "upstream_bindgen" }
3 changes: 0 additions & 3 deletions intel-sgx/attestation-daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ authors = ["Lily Sturmann <[email protected]>"]
edition = "2018"

[dependencies]
openssl = "0.10.23"
hex = "0.3.1"
dcap-ql = "0.2.0"
serde_json = "1.0.40"
bufstream = "0.1.4"
serde = { version = "1.0", features = ["derive"] }

[dependencies.sgx-isa]
Expand Down
17 changes: 8 additions & 9 deletions intel-sgx/attestation-daemon/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde_json::{from_reader, to_writer};
use std::error::Error;
use std::io::Write;
use std::net::{TcpListener, TcpStream};
use std::net::{Shutdown, TcpListener, TcpStream};

const LISTENER_CONN: &'static str = "localhost:1034";
const ENCLAVE_CONN: &'static str = "localhost:1032";
Expand All @@ -19,24 +19,23 @@ fn main() -> Result<(), Box<dyn Error>> {
// used as the target for the enclave's attestation Report.
let qe_ti = dcap_ql::target_info().expect("Could not retrieve QE target info.");

// Serialize the Target Info onto the stream to the enclave
let mut enclave_stream = TcpStream::connect(ENCLAVE_CONN)?;
serde_json::to_writer(&mut enclave_stream, &qe_ti)?;
enclave_stream.shutdown(std::net::Shutdown::Write)?;
to_writer(&mut enclave_stream, &qe_ti)?;
enclave_stream.shutdown(Shutdown::Write)?;

// The attestation daemon receives the Report back from the attesting enclave.
let report: sgx_isa::Report = serde_json::from_reader(&mut enclave_stream)?;
let report: sgx_isa::Report = from_reader(enclave_stream)?;

// The attestation daemon gets a Quote from the Quoting Enclave for the Report.
// The Quoting Enclave verifies the Report's MAC as a prerequisite for generating
// the Quote. The Quote is signed with the Quoting Enclave's Attestation Key.
let quote = dcap_ql::quote(&report).expect("Could not generate quote.");

// The attestation daemon sends the Quote to the tenant.
let mut tenant_stream = incoming_tenant_stream?;
tenant_stream.write(&quote)?;
to_writer(&mut tenant_stream, &quote)?;
tenant_stream.shutdown(Shutdown::Write)?;

println!("\nQuote successfully generated and sent to tenant...");
println!("\nQuote successfully generated and sent to tenant.");
}
Ok(())
}
11 changes: 9 additions & 2 deletions intel-sgx/attestation-enclave/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ edition = "2018"

[dependencies]
serde_json = "1.0"
bufstream = "0.1.4"
byteorder = "1"

# The sgx-isa crate allows the use of Fortanix's data structures
# The sgx-isa crate allows the use of Fortanix's data structures
# relating to SGX, ex. Report, TargetInfo. The sgxstd feature
# should be enabled when using std::os::fortanix_sgx functionality,
# ex. ENCLU[EGETKEY] and ENCLU[EREPORT]. Serde_support allows for the
Expand All @@ -17,3 +17,10 @@ bufstream = "0.1.4"
[dependencies.sgx-isa]
version = "0.3.1"
features = ["serde_support", "sgxstd"]

[dependencies.mbedtls]
git = "https://github.com/haraldh/rust-mbedtls"
branch = "upstream_bindgen"
package = "mbedtls"
default-features = false
features = ["rdrand", "sgx"]
186 changes: 172 additions & 14 deletions intel-sgx/attestation-enclave/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,192 @@
use byteorder::{ByteOrder, NativeEndian, ReadBytesExt};
use mbedtls::{
cipher::*,
ecp::{EcGroup, EcPoint},
hash::{Md, Type::Sha256},
pk::{EcGroupId, Pk},
rng::{CtrDrbg, Random, Rdseed},
};
use serde_json::{from_reader, to_writer, Deserializer};
use sgx_isa::Report;
use std::error::Error;
use std::net::TcpListener;
use std::{error::Error, io::Cursor, net::TcpListener};

const LISTENER_ADDR: &'static str = "localhost:1032";
const DAEMON_LISTENER_ADDR: &'static str = "localhost:1032";
const TENANT_LISTENER_ADDR: &'static str = "localhost:1066";

// This copies the enclave key to the report data
fn from_slice(bytes: &[u8]) -> [u8; 64] {
let mut array = [0; 64];
let bytes = &bytes[..array.len()]; // panics if not enough data
array.copy_from_slice(bytes);
array
}

// Creates an AES 256 GCM cipher instance with the symmetric key and initialization vector
// set for each decryption operation.
fn new_aes256gcm_decrypt_cipher(
symm_key: &[u8],
iv: &[u8],
) -> Result<Cipher<Decryption, Authenticated, AdditionalData>, Box<dyn Error>> {
let c = Cipher::<_, Authenticated, _>::new(
raw::CipherId::Aes,
raw::CipherMode::GCM,
(symm_key.len() * 8) as _,
)?;

Ok(c.set_key_iv(&symm_key, &iv)?)
}

// Creates an AES 256 GCM cipher instance with the symmetric key and initialization vector
// set for each encryption operation.
// TODO: This is redundant, but I can't return a Cipher<_, Authenticated, AdditionalData>, so I need two separate
// functions. How to fix?
fn new_aes256gcm_encrypt_cipher(
symm_key: &[u8],
iv: &[u8],
) -> Result<Cipher<Encryption, Authenticated, AdditionalData>, Box<dyn Error>> {
let c = Cipher::<_, Authenticated, _>::new(
raw::CipherId::Aes,
raw::CipherMode::GCM,
(symm_key.len() * 8) as _,
)?;

Ok(c.set_key_iv(&symm_key, &iv)?)
}

fn main() -> Result<(), Box<dyn Error>> {
println!("\nListening on {}....\n", LISTENER_ADDR);
println!(
"\nListening on {} and {}....\n",
DAEMON_LISTENER_ADDR, TENANT_LISTENER_ADDR
);

// The enclave handles each incoming connection from attestation daemon.
for stream in TcpListener::bind(LISTENER_ADDR).unwrap().incoming() {
let daemon_streams = TcpListener::bind(DAEMON_LISTENER_ADDR)?;
let tenant_streams = TcpListener::bind(TENANT_LISTENER_ADDR)?;

let curve = EcGroup::new(EcGroupId::SecP256R1)?;

// The enclave generates an EC key pair. The public key will be inserted into the ReportData
// field of the enclave's attestation Report, which will be transmitted to the tenant.
let mut entropy = Rdseed;
let mut rng = CtrDrbg::new(&mut entropy, None)?;
let mut ec_key = Pk::generate_ec(&mut rng, curve.clone())?;
Copy link

Choose a reason for hiding this comment

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

Does this need a check, if the point is on the curve and not identity? A quick search in the mbed-tls source didn't reveal such a check.

@npmccallum ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the update I'm about to push, I'm now using https://github.com/fortanix/rust-mbedtls/blob/master/mbedtls/src/pk/mod.rs#L312, though I'm not completely sure I'm using it correctly.

if !Pk::check_pair(&ec_key, &ec_key) {
panic!("Error generating EC key")
};
let ec_pub = ec_key.ec_public()?;

// The enclave handles incoming connections from attestation daemon.
//let mut stream = daemon_streams
// .incoming()
// .next()
// .unwrap_or(Err(std::io::ErrorKind::ConnectionReset.into()))?;

for stream in daemon_streams.incoming() {
Copy link

Choose a reason for hiding this comment

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

since we are only handling one connection, we could get rid of the for loop and instead use:

    // The enclave handles _one_ incoming connection from the attestation daemon.
    let mut stream = daemon_streams
        .incoming()
        .next()
        .unwrap_or(Err(std::io::ErrorKind::ConnectionReset.into()))?;

Copy link
Contributor Author

@lkatalin lkatalin Dec 2, 2019

Choose a reason for hiding this comment

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

For some reason, when I make this change, the program hangs. So I have left it alone for now.

let mut stream = stream?;

// The enclave receives the identity of the Quoting Enclave from the
// attestation daemon, in the form of a (serialized) TargetInfo
// attestation daemon, in the form of a serialized TargetInfo
// structure. The TargetInfo contains the measurement and attribute flags
// of the Quoting Enclave.
let qe_id: sgx_isa::Targetinfo = serde_json::from_reader(&mut stream)?;
let qe_id: sgx_isa::Targetinfo = from_reader(&mut stream)?;

// The enclave's public key will be transmitted to the tenant in the ReportData field
// of the enclave's attesation Report. It must be a &[u8; 64].
// The compressed public key is 33 bytes long and must be extended by 31 bytes.
let mut report_data = ec_pub.to_binary(&curve, true)?;
report_data.extend(&[0u8; 31]);
let report_data = from_slice(&report_data);

// The enclave creates a Report attesting its identity, with the Quoting
// Enclave (whose identity was just received) as the Report's target. The
// blank ReportData field must be passed in as a &[u8; 64].
let report = {
let report_data = [0u8; 64];
Report::for_target(&qe_id, &report_data)
};
// ReportData field contains the enclave's public key.
let report = Report::for_target(&qe_id, &report_data);

// The enclave sends its attestation Report back to the attestation daemon.
serde_json::to_writer(&mut stream, &report)?;
to_writer(&mut stream, &report)?;

println!("Successfully sent report to daemon.");

break;
}

// The enclave handles each incoming connection from the tenant. These channels between the tenant
// and enclave are established after attestation is verified and all data exchanged between the tenant
// and enclave after public keys are exchanged is encrypted with a shared symmetric key.
for stream in tenant_streams.incoming() {
let mut stream = stream?;

// The enclave receives and deserializes tenant pub key, ivs and tags for ciphertext values, ciphertext.
let deserializer = Deserializer::from_reader(stream.try_clone().unwrap());
let mut iterator = deserializer.into_iter::<Vec<u8>>();

let tenant_key = iterator.next().unwrap()?;
let hash_v1 = iterator.next().unwrap()?;
let hash_v2 = iterator.next().unwrap()?;
let iv1 = iterator.next().unwrap()?;
let iv2 = iterator.next().unwrap()?;
let tag1 = iterator.next().unwrap()?;
let tag2 = iterator.next().unwrap()?;
let ciphertext1 = iterator.next().unwrap()?;
let ciphertext2 = iterator.next().unwrap()?;

// The enclave generates a shared secret with the tenant. A SHA256 hash of this shared secret
// is used as the symmetric key for encryption and decryption of data.
let tenant_pubkey_ecpoint = EcPoint::from_binary(&curve, &tenant_key)?;
let tenant_pubkey = Pk::public_from_ec_components(curve.clone(), tenant_pubkey_ecpoint)?;

// TODO: Should this use the same rng as before or create a new one?
let mut shared_secret = [0u8; 32]; // 256 / 8
ec_key.agree(&tenant_pubkey, &mut shared_secret, &mut rng)?;
let mut symm_key = [0u8; 32];
Md::hash(Sha256, &shared_secret, &mut symm_key)?;

// These cipher instances are used for decryption operations and one encryption operation.
// TODO: Can the same cipher instance be used for these? Cipher doesn't implement clone().
let decrypt_cipher_1 = new_aes256gcm_decrypt_cipher(&symm_key, &iv1)?;
let decrypt_cipher_2 = new_aes256gcm_decrypt_cipher(&symm_key, &iv2)?;

let mut entropy = Rdseed;
let mut rng = CtrDrbg::new(&mut entropy, None)?;
let mut iv = [0u8; 16];
rng.random(&mut iv)?;
let encrypt_cipher = new_aes256gcm_encrypt_cipher(&symm_key, &iv)?;

// The values received from the tenant are decrypted.
let mut plaintext1 = [0u8; 32];
let mut plaintext2 = [0u8; 32];
Comment on lines +155 to +156
Copy link
Contributor

Choose a reason for hiding this comment

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

We should not artificially limit the plaintext length here.

let _ = decrypt_cipher_1.decrypt_auth(&hash_v1, &ciphertext1, &mut plaintext1, &tag1)?;
let _ = decrypt_cipher_2.decrypt_auth(&hash_v2, &ciphertext2, &mut plaintext2, &tag2)?;

// The values received from the tenant are converted back to 32-bit unsigned ints.
let num1 = Cursor::new(plaintext1).read_u32::<NativeEndian>()?;
let num2 = Cursor::new(plaintext2).read_u32::<NativeEndian>()?;

// The sum of the two plaintext values is calculated.
let sum: u32 = num1 + num2;
println!("\n{} + {} = {}", num1, num2, sum);

// The sum is converted from u32 to bytes to serve as input for the encryption function.
// The extra 5th byte is in case of overflow.
let mut sum_as_bytes = [0u8; 5];
NativeEndian::write_u32(&mut sum_as_bytes, sum);

// The sum is encrypted.
let mut ciphersum = [0u8; 5];
Copy link
Contributor

Choose a reason for hiding this comment

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

One more quick one I forgot, sorry!

let mut tag = [0u8; 16];
let mut hash_of_sum = [0u8; 32];
Md::hash(Sha256, &sum_as_bytes, &mut hash_of_sum)?;
let _ =
encrypt_cipher.encrypt_auth(&hash_of_sum, &sum_as_bytes, &mut ciphersum, &mut tag)?;

// The tag, iv, additional data, and encrypted sum are sent back to the tenant.
to_writer(&mut stream, &tag)?;
to_writer(&mut stream, &iv)?;
to_writer(&mut stream, &hash_of_sum)?;
to_writer(&mut stream, &ciphersum)?;

// TODO: This line exits the program after one run. Otherwise, it appears as though the tenant can be run
// again, but instead the program just hangs the second time. Why?
break;
Copy link

Choose a reason for hiding this comment

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

Well, you would have to open a new connection from the tenant with TcpStream::connect(ENCL_CONN) to get a new loop iteration without the break.

}

Ok(())
Expand Down
12 changes: 10 additions & 2 deletions intel-sgx/attestation-tenant/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ edition = "2018"
[dependencies]
sgx-isa = { version = "0.3.0" }
dcap-ql = "0.2.0"
failure = "0.1"
openssl = "0.10.23"
hex = "0.3.1"
failure = "0.1.5"
bufstream = "0.1.4"
serde_json = "1.0"
byteorder = "1"

[dependencies.mbedtls]
git = "https://github.com/haraldh/rust-mbedtls"
branch = "upstream_bindgen"
package = "mbedtls"
default-features = false
features = ["rdrand", "sgx"]
2 changes: 0 additions & 2 deletions intel-sgx/attestation-tenant/src/cert_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ impl CertChain {
panic!("Invalid issuer relationship in certificate chain.");
}
}
println!("Issuer relationships in PCK cert chain are valid...");
Ok(())
}

Expand Down Expand Up @@ -85,7 +84,6 @@ impl CertChain {
// verified.
context.init(&store, &self.leaf, &chain, |c| c.verify_cert())?;

println!("Signatures on certificate chain are valid...");
Ok(())
}
}
Loading