263 lines
7.7 KiB
C++
263 lines
7.7 KiB
C++
/*
|
|
Copyright (c) 2013, 2014 IOnU Security Inc. All rights reserved.
|
|
Created February 2014 by Kendrick Webster, based on code borrowed
|
|
from the K2Client and K2Daemon projects.
|
|
|
|
UdpIpc.cpp - implementation for UdpIpc.h
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "Hash.h"
|
|
#include "Random.h"
|
|
#include "UdpIpc.h"
|
|
|
|
/*
|
|
Packets consist of the following fields:
|
|
|
|
octets description
|
|
--------------------------------------------------------------------------
|
|
7 nonce (random initialization vector)
|
|
<n> body, NUL-terminated string
|
|
6 message integrity code (MIC)
|
|
|
|
All fields after the nonce are encrypted using an auto-hashing stream
|
|
cipher with a hard-coded key. The decrypted MIC will match only for
|
|
properly encrypted packets. Spurious packets fail the MIC check. This
|
|
'encryption' is used only for spurious packet rejection (not security).
|
|
|
|
Multi-byte fields are in network byte order (big endian).
|
|
*/
|
|
namespace ipc_constants
|
|
{
|
|
constexpr size_t NONCE_SIZE = 7;
|
|
constexpr size_t MIC_SIZE = 6;
|
|
constexpr size_t KEY_MIX_CYCLES = 32;
|
|
constexpr size_t CRYPT_SIZE = MIC_SIZE + 1; // +1 for NUL (string terminator)
|
|
constexpr size_t OVERHEAD_SIZE = CRYPT_SIZE + NONCE_SIZE;
|
|
constexpr size_t MAX_PACKET_SIZE = 1440;
|
|
constexpr size_t MAX_STRING_LENGTH = MAX_PACKET_SIZE - OVERHEAD_SIZE;
|
|
|
|
static const uint8_t KEY [] =
|
|
{
|
|
82, 144, 16, 86, 168, 144, 74, 102, 226, 166, 242, 99, 166, 54, 199, 118,
|
|
218, 71, 180, 240, 9, 234, 119, 252, 35, 35, 145, 79, 220, 4, 113, 63
|
|
};
|
|
|
|
static const uint8_t MIC [MIC_SIZE] =
|
|
{
|
|
236, 114, 80, 76, 10, 124
|
|
};
|
|
}
|
|
|
|
namespace this_module // local data
|
|
{
|
|
constexpr int INVALID_SOCKET = -1;
|
|
int sockfd = INVALID_SOCKET;
|
|
uint8_t tx_buf[ipc_constants::MAX_PACKET_SIZE];
|
|
uint8_t rx_buf[ipc_constants::MAX_PACKET_SIZE];
|
|
struct sockaddr_in recvaddr;
|
|
struct sockaddr_in servaddr;
|
|
}
|
|
|
|
success_t ionu::udp_ipc::InitSocket(uint16_t port, uint32_t timeout_milliseconds,
|
|
Log::severity_t trace_loglevel, Log::severity_t error_loglevel)
|
|
{
|
|
using this_module::INVALID_SOCKET;
|
|
using this_module::sockfd;
|
|
using this_module::servaddr;
|
|
|
|
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (INVALID_SOCKET == sockfd)
|
|
{
|
|
logprintf(error_loglevel, "socket: %s", strerror(errno));
|
|
return FAILURE;
|
|
}
|
|
|
|
bzero(&servaddr, sizeof(servaddr));
|
|
servaddr.sin_family = AF_INET;
|
|
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
servaddr.sin_port = htons(port);
|
|
if (0 != port)
|
|
{
|
|
logprintf(trace_loglevel, "listening on UDP port %d", port);
|
|
}
|
|
if (0 != bind(sockfd, reinterpret_cast<struct sockaddr *>(&servaddr), sizeof(servaddr)))
|
|
{
|
|
logprintf(error_loglevel, "bind: %s", strerror(errno));
|
|
return FAILURE;
|
|
}
|
|
if (0 == port)
|
|
{
|
|
socklen_t len = sizeof(servaddr);
|
|
if (0 == getsockname(sockfd, reinterpret_cast<struct sockaddr *>(&servaddr), &len))
|
|
{
|
|
logprintf(trace_loglevel, "IPC socket bound to UDP port %d", ntohs(servaddr.sin_port));
|
|
}
|
|
else
|
|
{
|
|
logprintf(error_loglevel, "getsockname: %s", strerror(errno));
|
|
}
|
|
}
|
|
|
|
struct timeval tv;
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = timeout_milliseconds * 1000;
|
|
if (0 != setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)))
|
|
{
|
|
logprintf(error_loglevel, "setsockopt: %s", strerror(errno));
|
|
return FAILURE;
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
// Encode a packet, returns length in octets or 0 if error
|
|
static unsigned int encodePacket(const std::string& body)
|
|
{
|
|
using namespace ipc_constants;
|
|
using this_module::tx_buf;
|
|
|
|
sc_hash_state_t h;
|
|
uint8_t* p = tx_buf;
|
|
unsigned int len = body.length();
|
|
|
|
if (len > MAX_STRING_LENGTH)
|
|
{
|
|
logprintf(Log::error, "string length (%u) too large for packet (%u max)", len, MAX_STRING_LENGTH);
|
|
return 0;
|
|
}
|
|
|
|
/* generate nonce and use it to key the stream cipher */
|
|
memset(&h, 0, sizeof(h));
|
|
ionu::random::GetRandom(p, NONCE_SIZE);
|
|
sc_hash_vector(&h, p, NONCE_SIZE);
|
|
sc_hash_vector(&h, KEY, sizeof(KEY));
|
|
sc_hash_mix(&h, KEY_MIX_CYCLES);
|
|
|
|
/* add body, encrypt packet, return length */
|
|
p += NONCE_SIZE;
|
|
memcpy(p, body.c_str(), len + 1);
|
|
memcpy(p + len + 1, MIC, MIC_SIZE);
|
|
sc_hash_encrypt(&h, tx_buf + NONCE_SIZE, len + CRYPT_SIZE);
|
|
return len + OVERHEAD_SIZE;
|
|
}
|
|
|
|
success_t ionu::udp_ipc::SendTo(ionu::network::CAddress a, const std::string& s)
|
|
{
|
|
using namespace this_module;
|
|
|
|
if (INVALID_SOCKET == sockfd)
|
|
{
|
|
logprintf(Log::critical, "IPC SendTo() was called before InitSocket");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ionu::random::SeedRandom();
|
|
unsigned int len = encodePacket(s);
|
|
if (0 == len)
|
|
{
|
|
return FAILURE;
|
|
}
|
|
else
|
|
{
|
|
struct sockaddr_in addr;
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = a.addr;
|
|
addr.sin_port = (PORT_LOOPBACK == a.port) ? servaddr.sin_port : a.port;
|
|
ssize_t n = sendto(sockfd, tx_buf, len, 0, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr));
|
|
if (n < 0)
|
|
{
|
|
logprintf(Log::error, "sendto: %s", strerror(errno));
|
|
return FAILURE;
|
|
}
|
|
else if (static_cast<unsigned int>(n) != len)
|
|
{
|
|
logprintf(Log::error, "sendto: sent %d of %u bytes", n, len);
|
|
return FAILURE;
|
|
}
|
|
else
|
|
{
|
|
return SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Decode a packet, returns <true> and sets <body> if packet is good
|
|
static bool decodePacket(unsigned int len, std::string& body)
|
|
{
|
|
using namespace ipc_constants;
|
|
using this_module::rx_buf;
|
|
using this_module::recvaddr;
|
|
using ionu::network::IP4AddressString;
|
|
|
|
sc_hash_state_t h;
|
|
uint8_t* p = rx_buf;
|
|
|
|
/* reject short packets */
|
|
if (len < OVERHEAD_SIZE)
|
|
{
|
|
logprintf(Log::debug3, "spurious (short) packet from %s:%d", IP4AddressString(recvaddr).c_str(), ntohs(recvaddr.sin_port));
|
|
return false;
|
|
}
|
|
|
|
/* decrypt packet */
|
|
memset(&h, 0, sizeof(h));
|
|
sc_hash_vector(&h, p, NONCE_SIZE);
|
|
sc_hash_vector(&h, KEY, sizeof(KEY));
|
|
sc_hash_mix(&h, KEY_MIX_CYCLES);
|
|
p += NONCE_SIZE;
|
|
sc_hash_decrypt(&h, p, len - NONCE_SIZE);
|
|
|
|
/* reject packet if MIC is wrong */
|
|
if (0 != memcmp(rx_buf + (len - MIC_SIZE), MIC, MIC_SIZE))
|
|
{
|
|
logprintf(Log::debug3, "spurious (bad MIC) packet from %s:%d", IP4AddressString(recvaddr).c_str(), ntohs(recvaddr.sin_port));
|
|
return false;
|
|
}
|
|
|
|
/* reject packet if NUL terminator for body (string) is missing */
|
|
if (0 != rx_buf[len - (MIC_SIZE + 1)])
|
|
{
|
|
logprintf(Log::warning, "malformed (missing NUL) packet from %s:%d", IP4AddressString(recvaddr).c_str(), ntohs(recvaddr.sin_port));
|
|
return false;
|
|
}
|
|
|
|
body = reinterpret_cast<char*>(p);
|
|
return true;
|
|
}
|
|
|
|
bool ionu::udp_ipc::ReceiveFrom(ionu::network::CAddress& a, std::string& s)
|
|
{
|
|
using namespace this_module;
|
|
using ionu::random::SeedRandom;
|
|
|
|
if (INVALID_SOCKET == sockfd)
|
|
{
|
|
logprintf(Log::critical, "IPC ReceiveFrom() was called before InitSocket");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
SeedRandom();
|
|
socklen_t recvaddr_len = sizeof(recvaddr);
|
|
int n = recvfrom(sockfd, rx_buf, sizeof(rx_buf), 0, reinterpret_cast<struct sockaddr *>(&recvaddr), &recvaddr_len);
|
|
|
|
if (n > 0)
|
|
{
|
|
if (decodePacket(n, s))
|
|
{
|
|
a.addr = recvaddr.sin_addr.s_addr;
|
|
a.port = recvaddr.sin_port;
|
|
SeedRandom();
|
|
return true;
|
|
}
|
|
}
|
|
else if (EAGAIN != errno)
|
|
{
|
|
logprintf(Log::critical, "recvfrom() returned %d", n);
|
|
logprintf(Log::critical, "recvfrom: %s", strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
SeedRandom();
|
|
return false;
|
|
}
|