Browse Source

Start on handshake message 2

Ian Goldberg 1 year ago
parent
commit
9e2268b554
2 changed files with 145 additions and 14 deletions
  1. 114 14
      Enclave/comms.cpp
  2. 31 0
      Enclave/comms.hpp

+ 114 - 14
Enclave/comms.cpp

@@ -81,11 +81,11 @@ struct NodeCommState {
 
     // The function to call after the last chunk of a message has been
     // received.  If in_msg_get_buf allocated memory, this function
-    // should deallocate it.  in_msg_size, in_msg_offset, and in_msg_buf
-    // will already have been reset when this function is called.  The
-    // uint32_t that is passed are the total size of the _decrypted_
-    // data and the original total size of the _encrypted_ chunks that
-    // was passed to in_msg_get_buf.
+    // should deallocate it.  in_msg_size, in_msg_offset,
+    // in_msg_plaintext_processed, and in_msg_buf will already have been
+    // reset when this function is called.  The uint32_t that is passed
+    // are the total size of the _decrypted_ data and the original total
+    // size of the _encrypted_ chunks that was passed to in_msg_get_buf.
     std::function<void(NodeCommState&,uint8_t*,uint32_t,uint32_t)>
         in_msg_received;
 
@@ -133,6 +133,13 @@ static uint8_t* default_in_msg_get_buf(NodeCommState &commst,
     return new uint8_t[max_plaintext_bytes];
 }
 
+static void handshake_1_msg_received(NodeCommState &nodest,
+    uint8_t *data, uint32_t plaintext_len, uint32_t);
+static void handshake_2_msg_received(NodeCommState &nodest,
+    uint8_t *data, uint32_t plaintext_len, uint32_t);
+static void handshake_3_msg_received(NodeCommState &nodest,
+    uint8_t *data, uint32_t plaintext_len, uint32_t);
+
 // Receive (at the server) the first handshake message
 static void handshake_1_msg_received(NodeCommState &nodest,
     uint8_t *data, uint32_t plaintext_len, uint32_t)
@@ -151,26 +158,108 @@ static void handshake_1_msg_received(NodeCommState &nodest,
         return;
     }
     sgx_ecc_state_handle_t ecc_handle;
-    sgx_ec256_public_t pubkey;
-    memmove(&pubkey, data, sizeof(pubkey));
+    sgx_ec256_public_t peer_pubkey;
+    memmove(&peer_pubkey, data, sizeof(peer_pubkey));
+    delete[] data;
     sgx_ecc256_open_context(&ecc_handle);
     int valid;
-    if (sgx_ecc256_check_point(&pubkey, ecc_handle, &valid) || !valid) {
+    if (sgx_ecc256_check_point(&peer_pubkey, ecc_handle, &valid) || !valid) {
         printf("Invalid public key received from node %hu\n",
             nodest.node_num);
         sgx_ecc256_close_context(ecc_handle);
         return;
     }
-    delete[] data;
 
     printf("Valid public key received from node %hu\n", nodest.node_num);
-    memmove(&nodest.handshake_peer_pubkey, &pubkey, sizeof(pubkey));
 
     // Create our own DH key pair
-    sgx_ecc256_create_key_pair(&nodest.handshake_privkey,
-        &nodest.handshake_pubkey, ecc_handle);
+    sgx_ec256_public_t our_pubkey;
+    sgx_ec256_private_t our_privkey;
+    sgx_ecc256_create_key_pair(&our_privkey, &our_pubkey, ecc_handle);
+
+    // Construct the shared secret
+    sgx_ec256_dh_shared_t sharedsecret;
+    sgx_ecc256_compute_shared_dhkey(&our_privkey, &peer_pubkey,
+        &sharedsecret, ecc_handle);
+    memset(&our_privkey, 0, sizeof(our_privkey));
+
+    // Compute H1(sharedsecret) and H2(sharedsecret)
+    sgx_sha_state_handle_t sha_handle;
+    sgx_sha256_hash_t h1, h2;
+
+    sgx_sha256_init(&sha_handle);
+    sgx_sha256_update((const uint8_t*)"\x01", 1, sha_handle);
+    sgx_sha256_update((uint8_t*)&sharedsecret, sizeof(sharedsecret),
+        sha_handle);
+    sgx_sha256_get_hash(sha_handle, &h1);
+    sgx_sha256_close(sha_handle);
+
+    sgx_sha256_init(&sha_handle);
+    sgx_sha256_update((const uint8_t*)"\x02", 1, sha_handle);
+    sgx_sha256_update((uint8_t*)&sharedsecret, sizeof(sharedsecret),
+        sha_handle);
+    sgx_sha256_get_hash(sha_handle, &h2);
+    sgx_sha256_close(sha_handle);
+
+    // Compute the server-to-client MAC
+    sgx_hmac_state_handle_t hmac_handle;
+    uint8_t srv_cli_mac[16];
+    sgx_hmac256_init(h1, 16, &hmac_handle);
+    sgx_hmac256_update((uint8_t*)&our_pubkey, sizeof(our_pubkey),
+        hmac_handle);
+    sgx_hmac256_update((uint8_t*)&peer_pubkey, sizeof(peer_pubkey),
+        hmac_handle);
+    sgx_hmac256_update((uint8_t*)&g_pubkey, sizeof(g_pubkey),
+        hmac_handle);
+    sgx_hmac256_update((uint8_t*)&nodest.pubkey, sizeof(nodest.pubkey),
+        hmac_handle);
+    sgx_hmac256_final(srv_cli_mac, 16, hmac_handle);
+    sgx_hmac256_close(hmac_handle);
+
+    // Compute the client-to-server MAC
+    uint8_t cli_srv_mac[16];
+    sgx_hmac256_init(((uint8_t*)h1)+16, 16, &hmac_handle);
+    sgx_hmac256_update((uint8_t*)&peer_pubkey, sizeof(peer_pubkey),
+        hmac_handle);
+    sgx_hmac256_update((uint8_t*)&our_pubkey, sizeof(our_pubkey),
+        hmac_handle);
+    sgx_hmac256_update((uint8_t*)&nodest.pubkey, sizeof(nodest.pubkey),
+        hmac_handle);
+    sgx_hmac256_update((uint8_t*)&g_pubkey, sizeof(g_pubkey),
+        hmac_handle);
+    sgx_hmac256_final(cli_srv_mac, 16, hmac_handle);
+    sgx_hmac256_close(hmac_handle);
+
+    // Sign the server-to-client MAC
+    sgx_ec256_signature_t srv_cli_sig;
+    sgx_ecdsa_sign(srv_cli_mac, 16, &g_privkey, &srv_cli_sig, ecc_handle);
 
     sgx_ecc256_close_context(ecc_handle);
+
+    // Get us ready to receive handshake message 3
+    nodest.in_msg_get_buf = default_in_msg_get_buf;
+    nodest.in_msg_received = handshake_3_msg_received;
+    nodest.handshake_step = HANDSHAKE_S_SENT_2;
+
+    // Send handshake message 2
+    nodest.message_start(sizeof(our_pubkey) + sizeof(srv_cli_sig));
+    nodest.message_data((uint8_t*)&our_pubkey, sizeof(our_pubkey));
+    nodest.message_data((uint8_t*)&srv_cli_sig, sizeof(srv_cli_sig));
+}
+
+static void handshake_2_msg_received(NodeCommState &nodest,
+    uint8_t *data, uint32_t plaintext_len, uint32_t)
+{
+    printf("Received handshake_2 message of %u bytes:\n", plaintext_len);
+    for (uint32_t i=0;i<plaintext_len;++i) {
+        printf("%02x", data[i]);
+    }
+    printf("\n");
+}
+
+static void handshake_3_msg_received(NodeCommState &nodest,
+    uint8_t *data, uint32_t plaintext_len, uint32_t)
+{
 }
 
 // Start a new outgoing message.  Pass the number of _plaintext_ bytes
@@ -469,8 +558,14 @@ bool ecall_chunk(nodenum_t node_num, const uint8_t *chunkdata,
     nodest.in_msg_offset += chunklen;
     if (nodest.in_msg_offset == nodest.in_msg_size) {
         // This was the last chunk; handle the received message
-        nodest.in_msg_received(nodest, nodest.in_msg_buf,
-            nodest.in_msg_plaintext_processed, nodest.in_msg_size);
+        uint8_t* buf = nodest.in_msg_buf;
+        uint32_t plaintext_processed = nodest.in_msg_plaintext_processed;
+        uint32_t msg_size = nodest.in_msg_size;
+        nodest.in_msg_buf = NULL;
+        nodest.in_msg_size = 0;
+        nodest.in_msg_offset = 0;
+        nodest.in_msg_plaintext_processed = 0;
+        nodest.in_msg_received(nodest, buf, plaintext_processed, msg_size);
     }
     return true;
 }
@@ -488,6 +583,11 @@ void NodeCommState::handshake_start()
 
     sgx_ecc256_close_context(ecc_handle);
 
+    // Get us ready to receive handshake message 2
+    in_msg_get_buf = default_in_msg_get_buf;
+    in_msg_received = handshake_2_msg_received;
+    handshake_step = HANDSHAKE_C_SENT_1;
+
     // Send the public key as the first message
     message_start(sizeof(handshake_pubkey));
 

+ 31 - 0
Enclave/comms.hpp

@@ -3,6 +3,37 @@
 
 #include "enclave_api.h"
 
+// The enclave-to-enclave communication protocol is as follows.  It
+// probably could just be attested TLS in a production environment, but
+// we're not implementing remote attestation at this time.  This means
+// that the list of other enclaves' public keys are currently just
+// blindly trusted, so add a remote attestation step to validate them if
+// you want to deploy this for real.
+//
+// The protocol starts with a Sign-and-MAC (SIGMA) handshake, in the
+// pre-specified peer setting.  The client is the lower-numbered node,
+// and the server is the higher-numbered node.  The protocol is:
+//
+// Message 1 C -> S: g^x
+// Message 2 S -> C: g^y, Sig_S(MAC_{H_1a(g^{xy})}(g^y, g^x, Pub_S, Pub_C)
+// Message 3 C -> S: Sig_C(MAC_{H_1b(g^{xy})}(g^x, g^y, Pub_C, Pub_S)
+//
+// where Pub_C and Pub_S are the long-term signature keys of C and S.
+//
+// After the handshake, the client-to-server AES-GCM key is set to
+// H_2a(g^{xy}) and the server-to-client AES-GCM key is set to
+// H_2b(g^{xy}).  H_na(x) and H_nb(x) are the first 128 bits and the
+// last 128 bits of SHA256(n || x) respectively.
+//
+// After the handshake, data is sent in logical messages, which are
+// divided into chunks of size at most FRAME_SIZE - SGX_AESGCM_MAC_SIZE
+// bytes of plaintext, which will expand to at most FRAME_SIZE bytes of
+// ciphertext.  The IV for the first chunk in each direction is
+// 0x01 0x00 0x00 ... 0x00 (remember they use different keys in the two
+// directions), and each chunk increments the IV in a little-endian
+// manner.  The MAC tag of SGX_AESGCM_MAC_SIZE bytes is at the end of
+// the chunk.
+
 bool comms_init_nodestate(const EnclaveAPINodeConfig *apinodeconfigs,
     nodenum_t num_nodes, nodenum_t my_node_num);