Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic authentication using HMAC for federates joining a federation #105

Merged
merged 46 commits into from
Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
544adc7
Add OpenSSL dependency in CMake.
hokeun Aug 2, 2022
fca0264
Add new message types for HMAC-authenticated in joining federation.
hokeun Aug 2, 2022
73f6b6a
Add code for sending RTI hello.
hokeun Aug 2, 2022
811b457
Add code for performing HMAC authentication on federate's side.
hokeun Aug 2, 2022
f83f58a
Merge branch 'main' of https://github.com/lf-lang/reactor-c into secu…
hokeun Aug 26, 2022
af769f3
Fixed typos
Sep 26, 2022
a17c5d3
Added HMAC check protocol demo
Jakio815 Sep 26, 2022
5d88b51
Fixed some typos
Jakio815 Sep 27, 2022
54c9c25
Changed protocol to include msg type & federate ID when creating HMAC…
Jakio815 Sep 27, 2022
ae31ef0
HMAC authentication finished. For build, CCmakeGenerator.java must be…
Jakio815 Sep 27, 2022
b424053
Add auth options in RTI_instance as bool type is_auth.
Jakio815 Oct 5, 2022
9cdc725
Fixed bug on RTI -a option
Jakio815 Oct 5, 2022
33bb32c
HMAC authorization only happens when FEDERATED_AUTH state
Jakio815 Oct 6, 2022
4da4fd8
RTI now optionally includes openssl with cmake option -DAUTH=ON
Jakio815 Oct 6, 2022
8f9792a
Merge branch 'main' into security
Jakio815 Oct 6, 2022
f866768
Update comments and make definition for federate authentication more …
hokeun Oct 7, 2022
ce27076
Update complier definition and comments.
hokeun Oct 7, 2022
f74bb5e
Make variable and parameter names more descriptive, separate #include…
hokeun Oct 7, 2022
6a23b50
Make RTI more robust so that it does not die when authentication fails.
hokeun Oct 8, 2022
968caca
Add comments for authentication messages.
hokeun Oct 8, 2022
ac904a7
Use more standard way to get required buffer length and encoding fed …
hokeun Oct 8, 2022
e4853d7
Add error handling for HMAC function.
hokeun Oct 8, 2022
2a3e39f
Remove unused message type.
hokeun Oct 8, 2022
c68b2fd
Move ifdef dependencies to top & Add comments.
Jakio815 Oct 30, 2022
d168550
Minory fixes, not finished
Jakio815 Oct 30, 2022
129e866
Merge branch 'main' of github.com:lf-lang/reactor-c into security
Jakio815 Oct 30, 2022
9354daa
Added comments
Jakio815 Oct 31, 2022
4f17f20
Added SHA256_HMAC_LENGTH & only print error not exit when wrong messa…
Jakio815 Oct 31, 2022
ea507c2
Merge branch 'main' of github.com:lf-lang/reactor-c into security
Jakio815 Nov 10, 2022
d6fc5ba
Updated lf-lang/lingua-franca version
Jakio815 Nov 14, 2022
4cea135
Update lingua-franca-ref.txt
Nov 16, 2022
8ed5649
Update lingua-franca-ref.txt
Nov 16, 2022
83491bf
Update linguq-franca-ref.txt
Nov 16, 2022
4ece789
Temporarily change ci test
Nov 16, 2022
e3352f0
Temporarily change ci test branch to auth
Nov 16, 2022
75d0f13
Update lingua-frnaca-ref.txt version
Nov 17, 2022
5dfbb52
Update lingua-franca-ref.txt
Nov 18, 2022
58a0c4c
Update lingua-franca.txt
Nov 18, 2022
5b80211
Update lingua-franca-ref.txt
Nov 19, 2022
268260f
Update lingua-franca-ref.txt
Nov 19, 2022
491cccc
Update lingua-franca-ref.txt
Nov 19, 2022
c10629b
More specific data types for Cpp compilers
Nov 21, 2022
78911bc
Restore ci test
Nov 21, 2022
0cfbcd7
Restore ci test
Nov 21, 2022
3254e8c
Updated lingua-franca.txt
Nov 21, 2022
d06c6ff
Fixed from MSG_TYPE_RTI_HELLO to MSG_TYPE_RTI_NONCE
Dec 21, 2022
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
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,30 @@ jobs:

lf-default:
needs: fetch-lf
uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master
uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@auth
with:
runtime-ref: ${{ github.ref }}
compiler-ref: ${{ needs.fetch-lf.outputs.ref }}

lf-gedf-np:
needs: fetch-lf
uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master
uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@auth
with:
runtime-ref: ${{ github.ref }}
compiler-ref: ${{ needs.fetch-lf.outputs.ref }}
scheduler: GEDF_NP

lf-gedf-np-ci:
needs: fetch-lf
uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master
uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@auth
with:
runtime-ref: ${{ github.ref }}
compiler-ref: ${{ needs.fetch-lf.outputs.ref }}
scheduler: GEDF_NP_CI

lf-adaptive:
needs: fetch-lf
uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@master
uses: lf-lang/lingua-franca/.github/workflows/c-tests.yml@auth
with:
Copy link
Collaborator

Choose a reason for hiding this comment

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

These changes are only for CI tests.

runtime-ref: ${{ github.ref }}
compiler-ref: ${{ needs.fetch-lf.outputs.ref }}
Expand Down
20 changes: 20 additions & 0 deletions core/federated/RTI/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@
# If you would like to go back to non-DEBUG mode, you would have to remove all
# contents of the `build` folder.

# To enable simple HMAC-based authentication of federates,
# add `-DAUTH=ON` option to the cmake command as shown below:
#
# $> mkdir build && cd build
# $> cmake -DAUTH=ON ../
# $> make
# $> sudo make install
#
# If you would like to go back to non-AUTH mode, you would have to remove all
# contents of the `build` folder.

cmake_minimum_required(VERSION 3.12)
project(RTI VERSION 1.0.0 LANGUAGES C)

Expand Down Expand Up @@ -74,6 +85,15 @@ target_compile_definitions(RTI PUBLIC NUMBER_OF_WORKERS)
find_package(Threads REQUIRED)
target_link_libraries(RTI Threads::Threads)

# Option for enabling federate authentication by RTI.
option(AUTH "Federate authentication by RTI enabled." OFF)
IF(AUTH MATCHES ON)
add_compile_definitions(__RTI_AUTH__)
# Find OpenSSL and link to it
find_package(OpenSSL REQUIRED)
target_link_libraries(RTI OpenSSL::SSL)
ENDIF(AUTH MATCHES ON)

install(
TARGETS RTI
DESTINATION bin
Expand Down
11 changes: 11 additions & 0 deletions core/federated/RTI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ sudo make install

If you would like to go back to the non-DEBUG mode, you would have to remove all contents of the `build` folder.

**Note:** To enable simple HMAC-based authentication of federates,
add `-DAUTH=ON` option to the cmake command as shown below:

```bash
mkdir build && cd build
cmake -DAUTH=ON ../
make
sudo make install
```

If you would like to go back to non-AUTH mode, you would have to remove all contents of the `build` folder.

To build a docker image for the RTI, do
```bash
Expand Down
86 changes: 84 additions & 2 deletions core/federated/RTI/rti.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "net_common.h" // Defines message types, etc. Includes <pthread.h> and "reactor.h".
#include "tag.c" // Time-related types and functions.
#include "rti.h"

#ifdef __RTI_AUTH__
#include <openssl/rand.h> // For secure random number generation.
#include <openssl/hmac.h> // For HMAC authentication.
#endif
/**
* The state of this RTI instance.
*/
Expand All @@ -87,7 +90,8 @@ RTI_instance_t _RTI = {
.socket_descriptor_UDP = -1,
.clock_sync_global_status = clock_sync_init,
.clock_sync_period_ns = MSEC(10),
.clock_sync_exchanges_per_interval = 10
.clock_sync_exchanges_per_interval = 10,
.authentication_enabled = false
};

/**
Expand Down Expand Up @@ -1796,6 +1800,69 @@ int receive_udp_message_and_set_up_clock_sync(int socket_id, uint16_t fed_id) {
return 1;
}

/**
* Authenticate incoming federate by performing HMAC-based authentication.
*
* @param socket Socket for the incoming federate tryting to authenticate.
* @return True if authentication is successful and false otherwise.
*/
#ifdef __RTI_AUTH__
bool authenticate_federate(int socket) {
// Buffer for message type and federation RTI nonce.
size_t message_length = 1 + NONCE_LENGTH;
unsigned char rti_hello_buffer[message_length];
rti_hello_buffer[0] = MSG_TYPE_RTI_HELLO;
unsigned char rti_nonce[NONCE_LENGTH];
RAND_bytes(rti_nonce, NONCE_LENGTH);
memcpy(rti_hello_buffer + 1, rti_nonce, NONCE_LENGTH);
// Send RTI hello with RTI's random nonce.
write_to_socket(socket, message_length, rti_hello_buffer);

// Check HMAC of received FED_RESPONSE message.
size_t hmac_length = SHA256_HMAC_LENGTH;
size_t federation_id_length = strnlen(_RTI.federation_id, 255);
size_t fed_id_length = sizeof(uint16_t);

unsigned char received[1 + NONCE_LENGTH + fed_id_length + hmac_length];
read_from_socket_errexit(socket, 1 + NONCE_LENGTH + fed_id_length + hmac_length, received, "Failed to read RTI response.");
if (received[0] != MSG_TYPE_FED_RESPONSE) {
lf_print_error("Received unexpected response %u from the FED (see net_common.h).",
received[0]);
return false;
}

// Create tag to compare to received tag.
unsigned char buf_to_check[1 + fed_id_length + NONCE_LENGTH];
buf_to_check[0] = MSG_TYPE_FED_RESPONSE;
memcpy(&buf_to_check[1], &received[1 + NONCE_LENGTH], fed_id_length);
memcpy(&buf_to_check[1 + fed_id_length], rti_nonce, NONCE_LENGTH);
unsigned char rti_tag[hmac_length];
HMAC(EVP_sha256(), _RTI.federation_id, federation_id_length, buf_to_check, 1 + fed_id_length + NONCE_LENGTH,
rti_tag, &hmac_length);

// Compare received tag and created tag.
if (memcmp(&received[1 + fed_id_length + NONCE_LENGTH], rti_tag, hmac_length) != 0) {
// Federation IDs do not match. Send back a HMAC_DOES_NOT_MATCH message.
lf_print_warning("HMAC authentication failed. Rejecting the federate.");
send_reject(socket, HMAC_DOES_NOT_MATCH);
return false;
}
else{
LF_PRINT_LOG("HMAC verified.");
// HMAC tag is created with MSG_TYPE and received federate nonce.
unsigned char mac_buf[1 + NONCE_LENGTH];
mac_buf[0] = MSG_TYPE_RTI_RESPONSE;
memcpy(&mac_buf[1], &received[1], NONCE_LENGTH);
// Buffer for message type and HMAC tag.
unsigned char sender[1 + hmac_length];
sender[0] = MSG_TYPE_RTI_RESPONSE;
HMAC(EVP_sha256(), _RTI.federation_id, federation_id_length, mac_buf, 1 + NONCE_LENGTH,
&sender[1], &hmac_length);
write_to_socket(socket, 1 + hmac_length, sender);
return true;
}
}
#endif

/**
* Wait for one incoming connection request from each federate,
Expand Down Expand Up @@ -1824,6 +1891,18 @@ void connect_to_federates(int socket_descriptor) {
}
}

// Send RTI hello when RTI -a option is on.
#ifdef __RTI_AUTH__
if (_RTI.authentication_enabled) {
if (!authenticate_federate(socket_id)) {
lf_print_warning("RTI failed to authenticate the incoming federate.");
// Ignore the federate that failed authentication.
i--;
continue;
}
}
#endif

// The first message from the federate should contain its ID and the federation ID.
int32_t fed_id = receive_and_check_fed_id_message(socket_id, (struct sockaddr_in*)&client_fd);
if (fed_id >= 0
Expand Down Expand Up @@ -2051,6 +2130,7 @@ void usage(int argc, char* argv[]) {
printf(" (period in nanoseconds, default is 5 msec). Only applies to 'on'.\n");
printf(" - exchanges-per-interval <n>: Controls the number of messages that are exchanged for each\n");
printf(" clock sync attempt (default is 10). Applies to 'init' and 'on'.\n\n");
printf(" -a, --auth Turn on HMAC authentication options.\n\n");

printf("Command given:\n");
for (int i = 0; i < argc; i++) {
Expand Down Expand Up @@ -2191,6 +2271,8 @@ int process_args(int argc, char* argv[]) {
}
i++;
i += process_clock_sync_args((argc-i), &argv[i]);
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--auth") == 0) {
_RTI.authentication_enabled = true;
} else if (strcmp(argv[i], " ") == 0) {
// Tolerate spaces
continue;
Expand Down
5 changes: 5 additions & 0 deletions core/federated/RTI/rti.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ typedef struct RTI_instance_t {
* Number of messages exchanged for each clock sync attempt.
*/
int32_t clock_sync_exchanges_per_interval;

/**
* Boolean indicating that authentication is enabled.
*/
bool authentication_enabled;
} RTI_instance_t;

#endif // RTI_H
90 changes: 90 additions & 0 deletions core/federated/federate.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "reactor_common.h"
#include "reactor_threaded.h"
#include "scheduler.h"
#ifdef FEDERATED_AUTHENTICATED
#include <openssl/rand.h> // For secure random number generation.
#include <openssl/hmac.h> // For HMAC-based authentication of federates.
#endif

// Error messages.
char* ERROR_SENDING_HEADER = "ERROR sending header information to federate via RTI";
Expand Down Expand Up @@ -859,6 +863,87 @@ void connect_to_federate(uint16_t remote_federate_id) {
}
}

#ifdef FEDERATED_AUTHENTICATED
/**
* Perform HMAC-based authentication with the RTI, using the federation ID
* as an HMAC key.
*
* @param rti_socket TCP socket for connection with the RTI.
*/
void perform_hmac_authentication(int rti_socket) {
lhstrh marked this conversation as resolved.
Show resolved Hide resolved
unsigned char buffer[1 + NONCE_LENGTH];
read_from_socket_errexit(rti_socket, 1 + NONCE_LENGTH, buffer,
"Failed to read RTI hello.");
if (buffer[0] != MSG_TYPE_RTI_HELLO) {
lf_print_error_and_exit(
"Received unexpected response %u from the RTI (see net_common.h).",
buffer[0]);
}
unsigned int hmac_length = SHA256_HMAC_LENGTH;
size_t federation_id_length = strnlen(federation_metadata.federation_id, 255);
// HMAC tag is created with MSG_TYPE, federate ID, received rti nonce.
unsigned char mac_buf[1 + sizeof(uint16_t) + NONCE_LENGTH];
mac_buf[0] = MSG_TYPE_FED_RESPONSE;
encode_uint16((uint16_t)_lf_my_fed_id, &mac_buf[1]);
memcpy(&mac_buf[1 + sizeof(uint16_t)], &buffer[1], NONCE_LENGTH);
unsigned char hmac_tag[hmac_length];
unsigned char * ret = HMAC(EVP_sha256(), federation_metadata.federation_id,
federation_id_length, mac_buf, 1 + sizeof(uint16_t) + NONCE_LENGTH,
hmac_tag, &hmac_length);
if (ret == NULL) {
lf_print_error_and_exit("HMAC failure for MSG_TYPE_FED_RESPONSE.");
}

// Buffer for message type, federate's nonce, federate ID, and HMAC tag.
unsigned char sender[1 + NONCE_LENGTH + sizeof(uint16_t) + hmac_length];
sender[0] = MSG_TYPE_FED_RESPONSE;
unsigned char federate_nonce[NONCE_LENGTH];
RAND_bytes(federate_nonce, NONCE_LENGTH);
int num_bytes = 1;
memcpy(&sender[num_bytes], federate_nonce, NONCE_LENGTH);
num_bytes += NONCE_LENGTH;
encode_uint16((uint16_t)_lf_my_fed_id, &sender[num_bytes]);
num_bytes += sizeof(uint16_t);
memcpy(&sender[num_bytes], hmac_tag, hmac_length);
num_bytes += hmac_length;
write_to_socket(rti_socket, num_bytes, sender);

// Received MSG_TYPE_RTI_RESPONSE
unsigned char received[1 + hmac_length];
read_from_socket_errexit(rti_socket, 1 + hmac_length, received,
"Failed to read RTI response.");
if (received[0] != MSG_TYPE_RTI_RESPONSE) {
lf_print_error_and_exit(
"Received unexpected response %u from the RTI (see net_common.h).",
received[0]);
}
// HMAC tag is created with MSG_TYPE and federate nonce.
unsigned char mac_buf2[1 + NONCE_LENGTH];
mac_buf2[0] = MSG_TYPE_RTI_RESPONSE;
memcpy(&mac_buf2[1], federate_nonce, NONCE_LENGTH);
unsigned char fed_tag[hmac_length];
ret = HMAC(EVP_sha256(), federation_metadata.federation_id, federation_id_length,
mac_buf2, 1 + NONCE_LENGTH, fed_tag, &hmac_length);
if (ret == NULL) {
lf_print_error_and_exit("HMAC failure for MSG_TYPE_RTI_RESPONSE.");
}
// Compare received tag and created tag.
if (memcmp(&received[1], fed_tag, hmac_length) != 0) {
// Federation IDs do not match. Send back a MSG_TYPE_REJECT message.
lf_print_error("HMAC authentication failed.");
unsigned char response[2];
response[0] = MSG_TYPE_REJECT;
response[1] = HMAC_DOES_NOT_MATCH;
write_to_socket_errexit(
rti_socket, 2, response,
"Federate failed to write MSG_TYPE_REJECT message on the socket.");
close(rti_socket);
} else {
LF_PRINT_LOG("HMAC verified.");
}
}
#endif

/**
* Connect to the RTI at the specified host and port and return
* the socket descriptor for the connection. If this fails, the
Expand Down Expand Up @@ -967,7 +1052,12 @@ void connect_to_rti(const char* hostname, int port) {
// Notify the RTI of the ID of this federate and its federation.
unsigned char buffer[4];

#ifdef FEDERATED_AUTHENTICATED
LF_PRINT_LOG("Connected to an RTI. Performing HMAC-based authentication using federation ID.");
perform_hmac_authentication(_fed.socket_TCP_RTI);
#else
LF_PRINT_LOG("Connected to an RTI. Sending federation ID for authentication.");
#endif

// Send the message type first.
buffer[0] = MSG_TYPE_FED_IDS;
Expand Down
Loading