|
| 1 | +use byteorder::{ByteOrder, NativeEndian, ReadBytesExt}; |
| 2 | +use mbedtls::{ |
| 3 | + cipher::*, |
| 4 | + ecp::{EcGroup, EcPoint}, |
| 5 | + hash::{Md, Type::Sha256}, |
| 6 | + pk::{EcGroupId, Pk}, |
| 7 | + rng::{CtrDrbg, Random, Rdseed}, |
| 8 | +}; |
| 9 | +use serde_json::{from_reader, to_writer, Deserializer}; |
1 | 10 | use sgx_isa::Report;
|
2 |
| -use std::error::Error; |
3 |
| -use std::net::TcpListener; |
| 11 | +use std::{error::Error, io::Cursor, net::TcpListener}; |
4 | 12 |
|
5 |
| -const LISTENER_ADDR: &'static str = "localhost:1032"; |
| 13 | +const DAEMON_LISTENER_ADDR: &'static str = "localhost:1032"; |
| 14 | +const TENANT_LISTENER_ADDR: &'static str = "localhost:1066"; |
| 15 | + |
| 16 | +// This copies the enclave key to the report data |
| 17 | +fn from_slice(bytes: &[u8]) -> [u8; 64] { |
| 18 | + let mut array = [0; 64]; |
| 19 | + let bytes = &bytes[..array.len()]; // panics if not enough data |
| 20 | + array.copy_from_slice(bytes); |
| 21 | + array |
| 22 | +} |
| 23 | + |
| 24 | +// Creates an AES 256 GCM cipher instance with the symmetric key and initialization vector |
| 25 | +// set for each decryption operation. |
| 26 | +fn new_aes256gcm_decrypt_cipher( |
| 27 | + symm_key: &[u8], |
| 28 | + iv: &[u8], |
| 29 | +) -> Result<Cipher<Decryption, Authenticated, AdditionalData>, Box<dyn Error>> { |
| 30 | + let c = Cipher::<_, Authenticated, _>::new( |
| 31 | + raw::CipherId::Aes, |
| 32 | + raw::CipherMode::GCM, |
| 33 | + (symm_key.len() * 8) as _, |
| 34 | + ) |
| 35 | + .unwrap(); |
| 36 | + |
| 37 | + Ok(c.set_key_iv(&symm_key, &iv)?) |
| 38 | +} |
| 39 | + |
| 40 | +// Creates an AES 256 GCM cipher instance with the symmetric key and initialization vector |
| 41 | +// set for each encryption operation. |
| 42 | +// TODO: This is redundant, but I can't return a Cipher<_, Authenticated, AdditionalData>, so I need two separate |
| 43 | +// functions. How to fix? |
| 44 | +fn new_aes256gcm_encrypt_cipher( |
| 45 | + symm_key: &[u8], |
| 46 | + iv: &[u8], |
| 47 | +) -> Result<Cipher<Encryption, Authenticated, AdditionalData>, Box<dyn Error>> { |
| 48 | + let c = Cipher::<_, Authenticated, _>::new( |
| 49 | + raw::CipherId::Aes, |
| 50 | + raw::CipherMode::GCM, |
| 51 | + (symm_key.len() * 8) as _, |
| 52 | + ) |
| 53 | + .unwrap(); |
| 54 | + |
| 55 | + Ok(c.set_key_iv(&symm_key, &iv)?) |
| 56 | +} |
6 | 57 |
|
7 | 58 | fn main() -> Result<(), Box<dyn Error>> {
|
8 |
| - println!("\nListening on {}....\n", LISTENER_ADDR); |
| 59 | + println!( |
| 60 | + "\nListening on {} and {}....\n", |
| 61 | + DAEMON_LISTENER_ADDR, TENANT_LISTENER_ADDR |
| 62 | + ); |
| 63 | + |
| 64 | + let daemon_streams = TcpListener::bind(DAEMON_LISTENER_ADDR)?; |
| 65 | + let tenant_streams = TcpListener::bind(TENANT_LISTENER_ADDR)?; |
| 66 | + |
| 67 | + let curve = EcGroup::new(EcGroupId::SecP256R1)?; |
| 68 | + |
| 69 | + // The enclave generates an EC key pair. The public key will be inserted into the ReportData |
| 70 | + // field of the enclave's attestation Report, which will be transmitted to the tenant. |
| 71 | + let mut entropy = Rdseed; |
| 72 | + let mut rng = CtrDrbg::new(&mut entropy, None)?; |
| 73 | + let mut ec_key = Pk::generate_ec(&mut rng, curve.clone())?; |
| 74 | + let ec_pub = ec_key.ec_public()?; |
9 | 75 |
|
10 | 76 | // 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() { |
12 | 78 | let mut stream = stream?;
|
13 | 79 |
|
14 | 80 | // 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 |
16 | 82 | // structure. The TargetInfo contains the measurement and attribute flags
|
17 | 83 | // 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); |
19 | 92 |
|
20 | 93 | // The enclave creates a Report attesting its identity, with the Quoting
|
21 | 94 | // 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); |
27 | 97 |
|
28 | 98 | // 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)?; |
30 | 100 |
|
31 | 101 | 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, ivs and tags for ciphertext values, 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 ad = iterator.next().unwrap()?; |
| 118 | + let iv1 = iterator.next().unwrap()?; |
| 119 | + let iv2 = iterator.next().unwrap()?; |
| 120 | + let tag1 = iterator.next().unwrap()?; |
| 121 | + let tag2 = iterator.next().unwrap()?; |
| 122 | + let ciphertext1 = iterator.next().unwrap()?; |
| 123 | + let ciphertext2 = iterator.next().unwrap()?; |
| 124 | + |
| 125 | + // The enclave generates a shared secret with the tenant. A SHA256 hash of this shared secret |
| 126 | + // is used as the symmetric key for encryption and decryption of data. |
| 127 | + let tenant_pubkey_ecpoint = EcPoint::from_binary(&curve, &tenant_key)?; |
| 128 | + let tenant_pubkey = Pk::public_from_ec_components(curve.clone(), tenant_pubkey_ecpoint)?; |
| 129 | + |
| 130 | + // TODO: Should this use the same rng as before or create a new one? |
| 131 | + let mut shared_secret = [0u8; 32]; // 256 / 8 |
| 132 | + ec_key.agree(&tenant_pubkey, &mut shared_secret, &mut rng)?; |
| 133 | + let mut symm_key = [0u8; 32]; |
| 134 | + Md::hash(Sha256, &shared_secret, &mut symm_key)?; |
| 135 | + |
| 136 | + // These cipher instances are used for decryption operations and one encryption operation. |
| 137 | + // TODO: Can the same cipher instance be used for these? Cipher doesn't implement clone(). |
| 138 | + let decrypt_cipher_1 = new_aes256gcm_decrypt_cipher(&symm_key, &iv1)?; |
| 139 | + let decrypt_cipher_2 = new_aes256gcm_decrypt_cipher(&symm_key, &iv2)?; |
| 140 | + |
| 141 | + let mut entropy = Rdseed; |
| 142 | + let mut rng = CtrDrbg::new(&mut entropy, None)?; |
| 143 | + let mut iv = [0u8; 16]; |
| 144 | + rng.random(&mut iv)?; |
| 145 | + let encrypt_cipher = new_aes256gcm_encrypt_cipher(&symm_key, &iv)?; |
| 146 | + |
| 147 | + // The values received from the tenant are decrypted. |
| 148 | + let mut plaintext1 = [0u8; 32]; |
| 149 | + let mut plaintext2 = [0u8; 32]; |
| 150 | + let _ = decrypt_cipher_1.decrypt_auth(&ad, &ciphertext1, &mut plaintext1, &tag1)?; |
| 151 | + let _ = decrypt_cipher_2.decrypt_auth(&ad, &ciphertext2, &mut plaintext2, &tag2)?; |
| 152 | + |
| 153 | + // The values received from the tenant are converted back to 32-bit unsigned ints. |
| 154 | + let num1 = Cursor::new(plaintext1).read_u32::<NativeEndian>()?; |
| 155 | + let num2 = Cursor::new(plaintext2).read_u32::<NativeEndian>()?; |
| 156 | + |
| 157 | + // The sum of the two plaintext values is calculated. |
| 158 | + let sum: u32 = num1 + num2; |
| 159 | + println!("\n{} + {} = {}", num1, num2, sum); |
| 160 | + |
| 161 | + // The sum is converted from u32 to bytes to serve as input for the encryption function. |
| 162 | + // The extra 5th byte is in case of overflow. |
| 163 | + let mut sum_as_bytes = [0u8; 5]; |
| 164 | + NativeEndian::write_u32(&mut sum_as_bytes, sum); |
| 165 | + |
| 166 | + // The sum is encrypted. |
| 167 | + let mut ciphersum = [0u8; 5]; |
| 168 | + let mut tag = [0u8; 16]; |
| 169 | + let _ = encrypt_cipher.encrypt_auth(&ad, &sum_as_bytes, &mut ciphersum, &mut tag)?; |
| 170 | + |
| 171 | + // The tag, iv, additional data, and encrypted sum are sent back to the tenant. |
| 172 | + to_writer(&mut stream, &tag)?; |
| 173 | + to_writer(&mut stream, &iv)?; |
| 174 | + to_writer(&mut stream, &ad)?; |
| 175 | + to_writer(&mut stream, &ciphersum)?; |
| 176 | + |
| 177 | + // TODO: This line exits the program after one run. Otherwise, it appears as though the tenant can be run |
| 178 | + // again, but instead the program just hangs the second time. Why? |
| 179 | + break; |
32 | 180 | }
|
33 | 181 |
|
34 | 182 | Ok(())
|
|
0 commit comments