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

Commit 3900cd0

Browse files
committed
Established encrypted channel between tenant and enclave using DHKE.
1 parent 6b70407 commit 3900cd0

File tree

9 files changed

+739
-132
lines changed

9 files changed

+739
-132
lines changed

intel-sgx/Cargo.lock

+406-91
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

intel-sgx/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ members = ['attestation-enclave', 'attestation-daemon', 'attestation-tenant']
44
[patch.crates-io]
55
dcap-ql = { git = "https://github.com/lkatalin/rust-sgx", branch = "serde-pck", rev = "aa81839e714ad7501e6ef44b2d4e61c8574548d4" }
66
sgx-isa = { git = "https://github.com/lkatalin/rust-sgx", branch = "serde-pck", rev = "aa81839e714ad7501e6ef44b2d4e61c8574548d4" }
7+
mbedtls = { git = "https://github.com/haraldh/rust-mbedtls", branch = "upstream_bindgen" }

intel-sgx/attestation-daemon/src/main.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use serde_json::{from_reader, to_writer};
12
use std::error::Error;
23
use std::io::Write;
3-
use std::net::{TcpListener, TcpStream};
4+
use std::net::{Shutdown, TcpListener, TcpStream};
45

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

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

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

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

35-
// The attestation daemon sends the Quote to the tenant.
35+
// The attestation daemon sends the Quote to the tenant. The Quote is already a Vec<u8>
36+
// and does not need serialization.
3637
let mut tenant_stream = incoming_tenant_stream?;
3738
tenant_stream.write(&quote)?;
3839

39-
println!("\nQuote successfully generated and sent to tenant...");
40+
println!("\nQuote successfully generated and sent to tenant.");
4041
}
4142
Ok(())
4243
}

intel-sgx/attestation-enclave/Cargo.toml

+8-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ edition = "2018"
88
serde_json = "1.0"
99
bufstream = "0.1.4"
1010

11-
# The sgx-isa crate allows the use of Fortanix's data structures
11+
# The sgx-isa crate allows the use of Fortanix's data structures
1212
# relating to SGX, ex. Report, TargetInfo. The sgxstd feature
1313
# should be enabled when using std::os::fortanix_sgx functionality,
1414
# ex. ENCLU[EGETKEY] and ENCLU[EREPORT]. Serde_support allows for the
@@ -17,3 +17,10 @@ bufstream = "0.1.4"
1717
[dependencies.sgx-isa]
1818
version = "0.3.1"
1919
features = ["serde_support", "sgxstd"]
20+
21+
[dependencies.mbedtls]
22+
git = "https://github.com/haraldh/rust-mbedtls"
23+
branch = "upstream_bindgen"
24+
package = "mbedtls"
25+
default-features = false
26+
features = ["rdrand", "sgx"]

intel-sgx/attestation-enclave/src/main.rs

+143-13
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,164 @@
1+
use mbedtls::{
2+
cipher::*,
3+
ecp::{EcGroup, EcPoint},
4+
hash::{Md, Type::Sha256},
5+
pk::{EcGroupId, Pk},
6+
rng::{CtrDrbg, Rdseed},
7+
};
8+
use serde_json::{from_reader, to_writer, Deserializer};
19
use sgx_isa::Report;
2-
use std::error::Error;
3-
use std::net::TcpListener;
10+
use std::{error::Error, net::TcpListener};
411

5-
const LISTENER_ADDR: &'static str = "localhost:1032";
12+
const DAEMON_LISTENER_ADDR: &'static str = "localhost:1032";
13+
const TENANT_LISTENER_ADDR: &'static str = "localhost:1066";
14+
15+
// This copies the enclave key to the report data
16+
fn from_slice(bytes: &[u8]) -> [u8; 64] {
17+
let mut array = [0; 64];
18+
let bytes = &bytes[..array.len()]; // panics if not enough data
19+
array.copy_from_slice(bytes);
20+
array
21+
}
22+
23+
// Creates an AES 256 CTR cipher instance with the symmetric key and initialization vector
24+
// set for each decryption operation.
25+
fn new_aes256ctr_decrypt_cipher(
26+
symm_key: &[u8],
27+
iv: &[u8],
28+
) -> Result<Cipher<Decryption, Traditional, CipherData>, Box<dyn Error>> {
29+
let c = Cipher::<_, Traditional, _>::new(
30+
raw::CipherId::Aes,
31+
raw::CipherMode::CTR,
32+
(symm_key.len() * 8) as _,
33+
)
34+
.unwrap();
35+
36+
Ok(c.set_key_iv(&symm_key, &iv)?)
37+
}
38+
39+
// Creates an AES 256 CTR cipher instance with the symmetric key and initialization vector
40+
// set for each encryption operation.
41+
// TODO: This is redundant, but I can't return a Cipher<_, Traditional, CipherData> so I need two separate
42+
// functions. How to fix?
43+
fn new_aes256ctr_encrypt_cipher(
44+
symm_key: &[u8],
45+
iv: &[u8],
46+
) -> Result<Cipher<Encryption, Traditional, CipherData>, Box<dyn Error>> {
47+
let c = Cipher::<_, Traditional, _>::new(
48+
raw::CipherId::Aes,
49+
raw::CipherMode::CTR,
50+
(symm_key.len() * 8) as _,
51+
)
52+
.unwrap();
53+
54+
Ok(c.set_key_iv(&symm_key, &iv)?)
55+
}
656

757
fn main() -> Result<(), Box<dyn Error>> {
8-
println!("\nListening on {}....\n", LISTENER_ADDR);
58+
println!(
59+
"\nListening on {} and {}....\n",
60+
DAEMON_LISTENER_ADDR, TENANT_LISTENER_ADDR
61+
);
62+
63+
let daemon_streams = TcpListener::bind(DAEMON_LISTENER_ADDR).unwrap();
64+
let tenant_streams = TcpListener::bind(TENANT_LISTENER_ADDR).unwrap();
65+
66+
// TODO: Is there a way to use RDRAND even though it's not a public module in mbedtls?
67+
let mut entropy = Rdseed;
68+
let mut rng = CtrDrbg::new(&mut entropy, None)?;
69+
let curve = EcGroup::new(EcGroupId::SecP256R1)?;
70+
71+
// The enclave generates an EC key pair. The public key is inserted into the ReportData
72+
// field of the enclave's attestation Report, which will be transmitted to the tenant.
73+
let mut ec_key = Pk::generate_ec(&mut rng, curve.clone())?;
74+
let ec_pub = ec_key.ec_public()?;
975

1076
// The enclave handles each incoming connection from attestation daemon.
11-
for stream in TcpListener::bind(LISTENER_ADDR).unwrap().incoming() {
77+
for stream in daemon_streams.incoming() {
1278
let mut stream = stream?;
1379

1480
// The enclave receives the identity of the Quoting Enclave from the
15-
// attestation daemon, in the form of a (serialized) TargetInfo
81+
// attestation daemon, in the form of a serialized TargetInfo
1682
// structure. The TargetInfo contains the measurement and attribute flags
1783
// of the Quoting Enclave.
18-
let qe_id: sgx_isa::Targetinfo = serde_json::from_reader(&mut stream)?;
84+
let qe_id: sgx_isa::Targetinfo = from_reader(&mut stream)?;
85+
86+
// The enclave's public key will be transmitted to the tenant in the ReportData field
87+
// of the enclave's attesation Report. It must be a &[u8; 64].
88+
// The compressed public key is 33 bytes long and must be extended by 31 bytes.
89+
let mut report_data = ec_pub.to_binary(&curve, true)?;
90+
report_data.extend(&[0u8; 31]);
91+
let report_data = from_slice(&report_data);
1992

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

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

31101
println!("Successfully sent report to daemon.");
102+
103+
break;
104+
}
105+
106+
// The enclave handles each incoming connection from the tenant. These channels between the tenant
107+
// and enclave are established after attestation is verified and all data exchanged between the tenant
108+
// and enclave after public keys are exchanged is encrypted with a shared symmetric key.
109+
for stream in tenant_streams.incoming() {
110+
let mut stream = stream?;
111+
112+
// The enclave receives and deserializes tenant pub key, iv, ciphertext.
113+
let deserializer = Deserializer::from_reader(stream.try_clone().unwrap());
114+
let mut iterator = deserializer.into_iter::<Vec<u8>>();
115+
116+
let tenant_key = iterator.next().unwrap()?;
117+
let iv = iterator.next().unwrap()?;
118+
let ciphertext1 = iterator.next().unwrap()?;
119+
let ciphertext2 = iterator.next().unwrap()?;
120+
121+
// The enclave generates a shared secret with the tenant. A SHA256 hash of this shared secret
122+
// is used as the symmetric key for encryption and decryption of data.
123+
let tenant_pubkey_ecpoint = EcPoint::from_binary(&curve, &tenant_key)?;
124+
let tenant_pubkey = Pk::public_from_ec_components(curve.clone(), tenant_pubkey_ecpoint)?;
125+
126+
let mut shared_secret = [0u8; 32]; // 256 / 8
127+
ec_key.agree(&tenant_pubkey, &mut shared_secret, &mut rng)?;
128+
129+
let mut symm_key = [0u8; 32];
130+
Md::hash(Sha256, &shared_secret, &mut symm_key)?;
131+
132+
// These cipher instances are used for decryption operations and one encryption operation.
133+
// TODO: Can the same cipher be used for these? Cipher doesn't implement clone().
134+
let decrypt_cipher_1 = new_aes256ctr_decrypt_cipher(&symm_key, &iv)?;
135+
let decrypt_cipher_2 = new_aes256ctr_decrypt_cipher(&symm_key, &iv)?;
136+
let encrypt_cipher = new_aes256ctr_encrypt_cipher(&symm_key, &iv)?;
137+
138+
let mut plaintext1 = [0u8; 32];
139+
let mut plaintext2 = [0u8; 32];
140+
let _ = decrypt_cipher_1.decrypt(&ciphertext1, &mut plaintext1)?;
141+
let _ = decrypt_cipher_2.decrypt(&ciphertext2, &mut plaintext2)?;
142+
143+
// TODO: Not have to index into this?
144+
let plaintext1 = plaintext1[0];
145+
let plaintext2 = plaintext2[0];
146+
147+
// The sum of the two plaintexts is calculated. The sum is encrypted and sent back to the tenant.
148+
let mut sum: Vec<u8> = Vec::new();
149+
sum.push(plaintext1 + plaintext2);
150+
151+
println!("\n{} + {} = {}", plaintext1, plaintext2, sum[0]);
152+
153+
let mut ciphersum = [0u8; 32];
154+
let _ = encrypt_cipher.encrypt(&sum, &mut ciphersum)?;
155+
let ciphersum = ciphersum[0];
156+
157+
to_writer(&mut stream, &ciphersum)?;
158+
159+
// TODO: This line exits the program after one run. Otherwise, it appears as though the tenant can be run
160+
// again, but instead the program just hangs the second time. Why?
161+
break;
32162
}
33163

34164
Ok(())

intel-sgx/attestation-tenant/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ openssl = "0.10.23"
1111
hex = "0.3.1"
1212
failure = "0.1.5"
1313
bufstream = "0.1.4"
14+
serde_json = "1.0"

intel-sgx/attestation-tenant/src/cert_chain.rs

-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ impl CertChain {
5454
panic!("Invalid issuer relationship in certificate chain.");
5555
}
5656
}
57-
println!("Issuer relationships in PCK cert chain are valid...");
5857
Ok(())
5958
}
6059

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

88-
println!("Signatures on certificate chain are valid...");
8987
Ok(())
9088
}
9189
}

intel-sgx/attestation-tenant/src/key.rs

+88-10
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use openssl::{
22
bn::BigNum,
3+
derive::Deriver,
34
ec::{EcGroup, EcKey},
45
hash::MessageDigest,
56
nid::Nid,
6-
pkey::{PKey, Public},
7+
pkey::{PKey, Private, Public},
78
sha,
89
sign::Verifier,
910
};
@@ -27,37 +28,114 @@ impl Display for HashError {
2728
}
2829
}
2930

30-
/// This is a wrapper for an openssl::PKey<Public> value that adds methods to create
31-
/// the key from raw x and y coordinates and verify a signature and SHA256 hash.
31+
/// This Key is a wrapper for an openssl::PKey<Public> and openssl::EcKey<Private> key pair
32+
/// with extra functionality, ex. the PKey can be created from raw x and y coordinates and verify
33+
/// a signature and SHA256 hash. The curve for all keys is SECP256R1 (known as PRIME256V1).
3234
pub struct Key {
33-
pkey: PKey<Public>,
35+
curve: EcGroup,
36+
pubkey: PKey<Public>,
37+
privkey: Option<EcKey<Private>>,
3438
}
3539

3640
impl Key {
37-
/// This creates a new Key from raw x and y coordinates for the SECP256R1 curve.
41+
/// This creates a new public PKey from raw x and y coordinates for the SECP256R1 curve.
42+
/// The private key is not known or needed.
3843
pub fn new_from_xy(xy_coords: &[u8]) -> Result<Self, Box<dyn Error>> {
39-
let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
44+
// TODO: Is it possible to give the Key a reference to this curve without instantiating it in each
45+
// Key instance? Rust doesn't do runtime-generated global variables.
46+
let curve = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
4047
let mut x: [u8; 32] = Default::default();
4148
let mut y: [u8; 32] = Default::default();
4249
x.copy_from_slice(&xy_coords[0..32]);
4350
y.copy_from_slice(&xy_coords[32..64]);
4451
let xbn = BigNum::from_slice(&x)?;
4552
let ybn = BigNum::from_slice(&y)?;
46-
let ec_key = EcKey::from_public_key_affine_coordinates(&group, &xbn, &ybn)?;
53+
let ec_key = EcKey::from_public_key_affine_coordinates(&curve, &xbn, &ybn)?;
4754
let pkey = PKey::from_ec_key(ec_key)?;
4855

49-
Ok(Key { pkey: pkey })
56+
Ok(Key {
57+
curve: curve,
58+
pubkey: pkey,
59+
privkey: None,
60+
})
61+
}
62+
63+
/// This creates a new public PKey from bytes. This can reconstruct a public key sent
64+
/// from an enclave, which uses mbedtls rather than openssl.
65+
pub fn new_from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn Error>> {
66+
let mut ctx = openssl::bn::BigNumContext::new()?;
67+
let curve = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
68+
let pub_ecpoint = openssl::ec::EcPoint::from_bytes(curve.as_ref(), &bytes, &mut *ctx)?;
69+
let pub_eckey = openssl::ec::EcKey::from_public_key(curve.as_ref(), pub_ecpoint.as_ref())?;
70+
let pub_pkey = openssl::pkey::PKey::from_ec_key(pub_eckey)?;
71+
72+
Ok(Key {
73+
curve: curve,
74+
pubkey: pub_pkey,
75+
privkey: None,
76+
})
5077
}
5178

5279
/// This creates a new Key from existing PKey value.
5380
pub fn new_from_pubkey(pkey: PKey<Public>) -> Self {
54-
Key { pkey: pkey }
81+
Key {
82+
curve: EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(),
83+
pubkey: pkey,
84+
privkey: None,
85+
}
86+
}
87+
88+
/// This creates a new elliptic curve key pair for the SECP256R1 curve with no other inputs.
89+
/// These are then converted to PKeys, which can be used for a DH key exchange according to
90+
/// https://github.com/sfackler/rust-openssl/blob/master/openssl/src/pkey.rs#L16. The EcKey type
91+
/// as the private key allows the public key to be returned as bytes in return_pubkey_bytes().
92+
// TODO: Is this a good curve to use for ECDH keys?
93+
pub fn new_pair_secp256r1() -> Result<Self, Box<dyn Error>> {
94+
let curve = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
95+
let eckey_priv = EcKey::generate(&curve)?;
96+
//let pkey_priv = PKey::from_ec_key(eckey_priv.clone())?;
97+
let eckey_pub = EcKey::from_public_key(&curve, eckey_priv.as_ref().public_key())?;
98+
let pkey_pub = PKey::from_ec_key(eckey_pub)?;
99+
Ok(Key {
100+
curve: curve,
101+
pubkey: pkey_pub,
102+
privkey: Some(eckey_priv),
103+
})
104+
}
105+
106+
/// Returns the Key's public key as a PKey<Public>
107+
pub fn return_pubkey(&self) -> &PKey<Public> {
108+
&self.pubkey
109+
}
110+
111+
/// Returns the Key's public key as bytes. This is useful for transmitting to the enclave, which
112+
/// can reconstruct the key with mbedtls.
113+
pub fn return_pubkey_bytes(&self) -> Result<Vec<u8>, Box<dyn Error>> {
114+
let mut new_ctx = openssl::bn::BigNumContext::new()?;
115+
let priv_key = self.privkey.as_ref().unwrap();
116+
117+
let tenant_pubkey_bytes = priv_key.public_key().to_bytes(
118+
&self.curve,
119+
openssl::ec::PointConversionForm::UNCOMPRESSED,
120+
&mut *new_ctx,
121+
)?;
122+
123+
Ok(tenant_pubkey_bytes)
124+
}
125+
126+
/// DHKE deriving shared secret between self's private key and peer's public key.
127+
pub fn derive_shared_secret(&self, peer_key: &PKey<Public>) -> Result<Vec<u8>, Box<dyn Error>> {
128+
let ec_priv_key = self.privkey.as_ref().unwrap();
129+
let pkey_priv_key = PKey::from_ec_key(ec_priv_key.clone())?;
130+
let mut deriver = Deriver::new(pkey_priv_key.as_ref())?;
131+
deriver.set_peer(peer_key)?;
132+
Ok(deriver.derive_to_vec()?)
55133
}
56134

57135
/// Given a signature and material that was signed with the Key's PKey value, this
58136
/// verifies the given signature.
59137
pub fn verify_sig(&self, signed: &[u8], sig: &Vec<u8>) -> Result<(), Box<dyn Error>> {
60-
let mut verifier = Verifier::new(MessageDigest::sha256(), &self.pkey)?;
138+
let mut verifier = Verifier::new(MessageDigest::sha256(), &self.pubkey)?;
61139
verifier.update(signed)?;
62140
verifier.verify(sig)?;
63141
Ok(())

0 commit comments

Comments
 (0)