Rework SCTP auth pipeline to allow dynamic key change
The SCTP key sharing mechanism won't be using a file to communicate
the active authentication key anymore as we will be receiving
it directly into message bridge through an AOS channel instead.
Change-Id: I46e079b98cbb6a0ed52fca36c67f7fa724ba249c
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/network/sctp_lib.cc b/aos/network/sctp_lib.cc
index 77cad02..c3c6e23 100644
--- a/aos/network/sctp_lib.cc
+++ b/aos/network/sctp_lib.cc
@@ -10,7 +10,10 @@
#include <unistd.h>
#include <algorithm>
+#include <cerrno>
+#include <fstream>
#include <string_view>
+#include <vector>
#include "aos/util/file.h"
@@ -31,9 +34,9 @@
struct sctp_sndrcvinfo sndrcvinfo;
} _sctp_cmsg_data_t;
+#if HAS_SCTP_AUTH
// Returns true if SCTP authentication is available and enabled.
bool SctpAuthIsEnabled() {
-#if HAS_SCTP_AUTH
struct stat current_stat;
if (stat("/proc/sys/net/sctp/auth_enable", ¤t_stat) != -1) {
int value = std::stoi(
@@ -45,11 +48,19 @@
LOG(WARNING) << "/proc/sys/net/sctp/auth_enable doesn't exist.";
return false;
}
-#else
- return false;
-#endif
}
+std::vector<uint8_t> GenerateSecureRandomSequence(size_t count) {
+ std::ifstream rng("/dev/random", std::ios::in | std::ios::binary);
+ CHECK(rng) << "Unable to open /dev/random";
+ std::vector<uint8_t> out(count, 0);
+ rng.read(reinterpret_cast<char *>(out.data()), count);
+ CHECK(rng) << "Couldn't read from random device";
+ rng.close();
+ return out;
+}
+#endif
+
} // namespace
bool Ipv6Enabled() {
@@ -289,28 +300,22 @@
sizeof(subscribe)) == 0);
}
- if (!auth_key_.empty()) {
- CHECK(SctpAuthIsEnabled())
- << "SCTP Authentication is disabled. Enable it with 'sysctl -w "
- "net.sctp.auth_enable=1' and try again.";
#if HAS_SCTP_AUTH
- // Set up the key with id `1`.
- sctp_authkey *const authkey =
- (sctp_authkey *)malloc(sizeof(sctp_authkey) + auth_key_.size());
- authkey->sca_keynumber = 1;
- authkey->sca_keylength = auth_key_.size();
- authkey->sca_assoc_id = SCTP_ALL_ASSOC;
- memcpy(&authkey->sca_key, auth_key_.data(), auth_key_.size());
+ if (sctp_authentication_) {
+ CHECK(SctpAuthIsEnabled())
+ << "SCTP Authentication key requested, but authentication isn't "
+ "enabled... Use `sysctl -w net.sctp.auth_enable=1` to enable";
- PCHECK(setsockopt(fd(), IPPROTO_SCTP, SCTP_AUTH_KEY, authkey,
- sizeof(sctp_authkey) + auth_key_.size()) == 0);
- free(authkey);
+ // Unfortunately there's no way to delete the null key if we don't have
+ // another key active so this is the only way to prevent unauthenticated
+ // traffic until the real shared key is established.
+ SetAuthKey(GenerateSecureRandomSequence(16));
- // Set key `1` as active.
+ // Disallow the null key.
struct sctp_authkeyid authkeyid;
- authkeyid.scact_keynumber = 1;
+ authkeyid.scact_keynumber = 0;
authkeyid.scact_assoc_id = SCTP_ALL_ASSOC;
- PCHECK(setsockopt(fd(), IPPROTO_SCTP, SCTP_AUTH_ACTIVE_KEY, &authkeyid,
+ PCHECK(setsockopt(fd(), IPPROTO_SCTP, SCTP_AUTH_DELETE_KEY, &authkeyid,
sizeof(authkeyid)) == 0);
// Set up authentication for data chunks.
@@ -319,13 +324,8 @@
PCHECK(setsockopt(fd(), IPPROTO_SCTP, SCTP_AUTH_CHUNK, &authchunk,
sizeof(authchunk)) == 0);
-
- // Disallow the null key.
- authkeyid.scact_keynumber = 0;
- PCHECK(setsockopt(fd(), IPPROTO_SCTP, SCTP_AUTH_DELETE_KEY, &authkeyid,
- sizeof(authkeyid)) == 0);
-#endif
}
+#endif
DoSetMaxSize();
}
@@ -335,6 +335,8 @@
std::optional<struct sockaddr_storage> sockaddr_remote,
sctp_assoc_t snd_assoc_id) {
CHECK(fd_ != -1);
+ LOG_IF(FATAL, sctp_authentication_ && current_key_.empty())
+ << "Expected SCTP authentication but no key active";
struct iovec iov;
iov.iov_base = const_cast<char *>(data.data());
iov.iov_len = data.size();
@@ -392,9 +394,6 @@
return true;
}
-SctpReadWrite::SctpReadWrite(std::vector<uint8_t> auth_key)
- : auth_key_(std::move(auth_key)) {}
-
void SctpReadWrite::FreeMessage(aos::unique_c_ptr<Message> &&message) {
if (use_pool_) {
free_messages_.emplace_back(std::move(message));
@@ -432,6 +431,8 @@
// fragmented. If we do end up with a fragment, then we copy the data out of it.
aos::unique_c_ptr<Message> SctpReadWrite::ReadMessage() {
CHECK(fd_ != -1);
+ LOG_IF(FATAL, sctp_authentication_ && current_key_.empty())
+ << "Expected SCTP authentication but no key active";
while (true) {
aos::unique_c_ptr<Message> result = AcquireMessage();
@@ -697,6 +698,55 @@
return false;
}
+void SctpReadWrite::SetAuthKey(absl::Span<const uint8_t> auth_key) {
+ PCHECK(fd_ != -1);
+ if (auth_key.empty()) {
+ return;
+ }
+ // We are already using the key, nothing to do.
+ if (auth_key == current_key_) {
+ return;
+ }
+#if !(HAS_SCTP_AUTH)
+ LOG(FATAL) << "SCTP Authentication key requested, but authentication isn't "
+ "available... You may need a newer kernel";
+#else
+ LOG_IF(FATAL, !SctpAuthIsEnabled())
+ << "SCTP Authentication key requested, but authentication isn't "
+ "enabled... Use `sysctl -w net.sctp.auth_enable=1` to enable";
+ // Set up the key with id `1`.
+ std::unique_ptr<sctp_authkey> authkey(
+ (sctp_authkey *)malloc(sizeof(sctp_authkey) + auth_key.size()));
+
+ authkey->sca_keynumber = 1;
+ authkey->sca_keylength = auth_key.size();
+ authkey->sca_assoc_id = SCTP_ALL_ASSOC;
+ memcpy(&authkey->sca_key, auth_key.data(), auth_key.size());
+
+ if (setsockopt(fd(), IPPROTO_SCTP, SCTP_AUTH_KEY, authkey.get(),
+ sizeof(sctp_authkey) + auth_key.size()) != 0) {
+ if (errno == EACCES) {
+ // TODO(adam.snaider): Figure out why this fails when expected nodes are
+ // not connected.
+ PLOG_EVERY_N(ERROR, 100) << "Setting authentication key failed";
+ return;
+ } else {
+ PLOG(FATAL) << "Setting authentication key failed";
+ }
+ }
+
+ // Set key `1` as active.
+ struct sctp_authkeyid authkeyid;
+ authkeyid.scact_keynumber = 1;
+ authkeyid.scact_assoc_id = SCTP_ALL_ASSOC;
+ if (setsockopt(fd(), IPPROTO_SCTP, SCTP_AUTH_ACTIVE_KEY, &authkeyid,
+ sizeof(authkeyid)) != 0) {
+ PLOG(FATAL) << "Setting key id `1` as active failed";
+ }
+ current_key_.assign(auth_key.begin(), auth_key.end());
+#endif
+} // namespace message_bridge
+
void Message::LogRcvInfo() const {
LOG(INFO) << "\tSNDRCV (stream=" << header.rcvinfo.rcv_sid
<< " ssn=" << header.rcvinfo.rcv_ssn