Sleds/libeye/eyelog.cpp

1099 lines
35 KiB
C++
Raw Normal View History

2025-03-13 21:28:38 +00:00
// Copyright (c) 2014-2015 IONU Security, Inc. All rights reserved.
//
// Logging and auditing services
#ifndef WIN32
#include <syslog.h>
#endif
#ifdef __APPLE__
#include "TargetConditionals.h"
#endif
#ifdef ANDROID
#include <android/log.h>
#endif
#ifdef WIN32
#include <winsock2.h>
#endif
#include <sys/stat.h>
#include <pthread.h>
#include "eyelog.h"
#include "eyetime.h"
#include "eyeutils.h"
#ifdef WIN32
#define LOG_TO_UDP_514_LOCALHOST // supports UDP trace log viewer
#endif
using namespace sequencelogic;
using namespace std;
namespace sequencelogic {
static const char* _logLevels[] = {"Audit", "Fatal", "Error", "Warning", "Info", "Debug", "Trace"};
static const char* _logLevelsU[] = {"AUDIT", "FATAL", "ERROR", "WARNING", "INFO", "DEBUG", "TRACE"};
// static const char* _defaultFormat = "[%L]|%c|%t|%m"; // This is the old libionu style
static const char* _defaultFormat = "%l:%c[%t] %m";
};
pthread_mutex_t ionu_logger_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t ionu_audit_mutex = PTHREAD_MUTEX_INITIALIZER;
namespace sequencelogic {
EyeLog* EyeLog::_instance = NULL;
}
void sequencelogic::logsprintf(std::string& msg, const char *s)
{
if (s)
msg += s;
}
#ifdef LOG_USE_VA_STYLE
// VS 2012 does not support variadic templates so use the old dangerous vargs style code
void sequencelogic::logsprintf (std::string& msg, const char *s, ...)
{
if (s == NULL) return;
va_list args;
va_start (args, s);
logvasprintf (msg, s, args);
va_end (args);
}
void sequencelogic::LogWrapper (EyeLog::LOG_LEVEL level, const char *s, ...)
{
if (s == NULL) return;
va_list args;
va_start (args, s);
std::string msg = "";
logvasprintf (msg, s, args);
va_end (args);
EyeLog::GetInstance()->Log (level, msg);
}
void sequencelogic::AuditWrapper (EyeLog::AUDIT_CODE code, const char *s, ...)
{
if (s == NULL) return;
va_list args;
va_start (args, s);
std::string msg = "";
logvasprintf (msg, s, args);
va_end (args);
EyeLog::GetInstance()->Audit (code, msg);
}
void sequencelogic::logvasprintf (std::string& msg, const char *fmt, va_list args)
{
char buffer[1024];
// Windows wants the buffer initialized to NULL
memset(buffer, 0, sizeof(buffer));
int n = vsnprintf(buffer, sizeof(buffer) - 1, fmt, args);
#ifdef WIN32
// On Windows, n is less than zero if the number of characters
// to write is greater than count (1024). The return value does
// not include the terminating null.
if (n < 0) {
buffer[1023] = '\0';
}
n = 1023;
// On other platforms, n is less than zero when an encoding error
// occurs.
#endif
if (n > 0 && n < 1024)
msg += buffer;
else if (n > 0) { // not enough space
char* bigbuf = new char[n+1];
if (bigbuf) {
int bn = vsnprintf (buffer, (size_t)n, fmt, args);
if (bn == n)
msg += bigbuf;
delete[] bigbuf;
}
}
}
#endif
EyeLog* EyeLog::GetInstance ()
{
if (_instance == NULL) {
pthread_mutex_lock (&ionu_logger_mutex);
if (_instance == NULL) // Check again as race may have had thread waiting on mutex
_instance = new EyeLog ();
pthread_mutex_unlock (&ionu_logger_mutex);
}
return _instance;
}
void EyeLog::ProcessFormat (const std::string& fmt)
{
if (fmt.size() == 0)
format = _defaultFormat;
else
format = fmt;
int a = 0;
std::string::iterator fmt_pos (format.begin());
std::string::iterator const fmt_end (format.end());
for (; fmt_pos != fmt_end; ++fmt_pos) {
if (*fmt_pos == '%' && *(++fmt_pos) != '%') {
switch (*fmt_pos) {
case 'l' : formatargs[a++] = IONU_LEVEL; break;
case 'L' : formatargs[a++] = IONU_ULEVEL; break;
case 'c' : formatargs[a++] = IONU_CLIENT; break;
case 'e' : formatargs[a++] = IONU_ETIME; break;
case 't' : formatargs[a++] = IONU_TIME; break;
case 'm' : formatargs[a++] = IONU_MESSAGE; break;
default : formatargs[a++] = IONU_TERMINAL; break;
}
}
}
formatargs[a] = IONU_TERMINAL;
}
const char* EyeLog::AuditCodeString (int auditcode)
{
switch (auditcode) {
#define X(code,value,description) \
case value : return #description;
IONU_AUDIT_CODES
#undef X
default : return "UnknownAuditCode";
}
}
bool EyeLog::ConfigLogger (const std::string& client, const std::string& device, LOG_LEVEL level, LOG_SINK sink, const std::string& fmt)
{
// Must specify a valid client id for the log
if (client.size() == 0)
return false;
// This is so an application's settings do not get overridden
if (clientId.size() > 0 && clientId.compare (client) != 0)
return true;
bool rc = true;
pthread_mutex_lock (&ionu_logger_mutex);
ProcessFormat (fmt);
clientId = client;
deviceId = device;
logLevel = level;
localSink = sink;
#if defined(ANDROID)
#elif defined (LOG_TO_SYSLOG)
if (localSink == IONU_SYSLOG)
openlog (client.c_str(), LOG_PID, LOG_USER); // Writes to /var/log/syslog
#elif defined(__APPLE__)
if (aslClientHandle == 0)
aslClientHandle = asl_open (clientId.c_str(), "com.ionu.IOnU", 0);
if (aslClientHandle) {
int priority = AIONU_LEVEL_INFO;
switch (level){
case IONU_FATAL: priority = AIONU_LEVEL_CRIT; break;
case IONU_ERROR: priority = AIONU_LEVEL_ERR; break;
case IONU_WARN: priority = AIONU_LEVEL_WARNING; break;
case IONU_INFO: priority = AIONU_LEVEL_INFO; break;
case IONU_DEBUG:
case IONU_TRACE:
default: priority = AIONU_LEVEL_DEBUG; break;
}
asl_set_filter (aslClientHandle, AIONU_FILTER_MASK_UPTO (priority));
}
#elif defined(WIN32)
if (sysLogFilename.size() <= 1) {
char* homePath = NULL;
homePath = getenv("USERPROFILE"); // Windows 7
if (!homePath)
homePath = getenv("HOMEPATH"); // WindowsXP
if (!homePath)
homePath = getenv("TEMP"); // Fallback
if (!homePath)
homePath = getenv ("TMP"); // Fall further back
if (!homePath)
homePath = "";
sysLogFilename = homePath;
if (sysLogFilename.size() > 1)
sysLogFilename += "/";
sysLogFilename += clientId;
sysLogFilename += ".log";
sysLogFile.open (sysLogFilename, ios::out | ios::binary);
if (!sysLogFile.is_open()) {
IONUERROR ("Unable to open system logfile (%s)", sysLogFilename.c_str());
rc = false;
}
}
#endif
pthread_mutex_unlock (&ionu_logger_mutex);
return rc;
}
bool EyeLog::ConfigFileLogger (const std::string& client, const std::string& dev, LOG_LEVEL level,
const std::string& log, LOG_SINK sink, const std::string& fmt)
{
if (!sequencelogic::IsValidFilename (log))
return false;
bool rc = true;
pthread_mutex_lock (&ionu_logger_mutex);
ProcessFormat (fmt);
clientId = client;
deviceId = dev;
logLevel = level;
localSink = sink;
// If changing log file, close old, open new
if (logFilename.compare (log) != 0) {
logFilename = log;
if (logFile.is_open())
logFile.close();
// Empty name closes file logging, otherwise open
if (logFilename.size() > 0) {
struct stat buf;
// Open existing logfile for append
if (stat (logFilename.c_str(), &buf) != -1) {
logFile.open (logFilename, ios::out | ios::binary | ios::app | ios::ate);
if (!logFile.is_open()) {
pthread_mutex_unlock (&ionu_logger_mutex);
IONUERROR ("Unable to open logfile (%s)", logFilename.c_str());
rc = false;
}
else {
logFileSize = (size_t)logFile.tellp();
}
}
// Create the first logfile
else {
logFile.open (logFilename, ios::out | ios::binary);
if (!logFile.is_open()) {
pthread_mutex_unlock (&ionu_logger_mutex);
IONUERROR ("Unable to create logfile (%s)", logFilename.c_str());
rc = false;
}
else {
if (deviceId.size() > 0) {
EyeTime now;
string header;
header = clientId;
header += ":[";
header += now.GetISO8601Time();
header += "] ";
header += deviceId;
logFile << header << endl;
logFileSize = header.size() + SL_ENDL_LEN;
}
else
logFileSize = 0;
}
}
}
}
if (rc)
pthread_mutex_unlock (&ionu_logger_mutex);
return rc;
}
bool EyeLog::ConfigFileLogger (const char* client, const char* dev, LOG_LEVEL level,
const char* log, LOG_SINK sink, const char* fmt)
{
std::string s_client(client);
std::string s_dev(dev);
std::string s_log(log);
std::string s_fmt(fmt);
return ConfigFileLogger(s_client, s_dev, level, s_log, sink, s_fmt);
}
bool EyeLog::CloseLogger (const std::string& logfile)
{
if (_instance) {
if (_instance->logFile.is_open())
_instance->logFile.close();
// Close the system lo
if (logfile.size() > 0) {
if (_instance->auditFile.is_open())
_instance->auditFile.close();
#ifdef WIN32
if (_instance->sysLogFile.is_open())
_instance->sysLogFile.close();
#elif defined (ANDROID)
#elif defined (LOG_TO_SYSLOG)
if (_instance->localSink == IONU_SYSLOG)
closelog();
#elif defined(__APPLE__)
if (_instance->aslClientHandle) {
asl_close (_instance->aslClientHandle);
_instance->aslClientHandle = 0;
}
#endif
}
delete _instance;
_instance = NULL;
}
return true;
}
bool EyeLog::CloseLogger (void)
{
std::string logfile = "";
return CloseLogger(logfile);
}
EyeLog::EyeLog ()
{
_instance = nullptr;
#ifdef DEBUG
logLevel = IONU_DEBUG;
localSink = IONU_CONSOLE;
#else
logLevel = IONU_WARN;
localSink = IONU_CONSOLE;
#endif
format = _defaultFormat;
formatargs[0] = IONU_LEVEL;
formatargs[1] = IONU_CLIENT;
formatargs[2] = IONU_TIME;
formatargs[3] = IONU_MESSAGE;
formatargs[4] = IONU_TERMINAL;
formatargs[5] = IONU_TERMINAL;
clientId = "";
deviceId = "";
logFileSize = 0;
maxLogFiles = IONU_MAX_LOG_FILES;
maxLogFileSize = IONU_MAX_LOG_FILE_SIZE;
auditId = 1;
unsigned char buff[2*SL_AES_KEY_LEN];
sequencelogic::MemSet (buff, 2*SL_AES_KEY_LEN, 0);
auditKey = new Key ("hmkey", "Audit log key", 2*SL_AES_KEY_LEN, buff, Key::AESHMAC);
auditKeyring = new Keyring ("Auditor", "PK and AES keys");
auditKeyring->AddKey (auditKey);
logFilename = "";
auditCachePath = "";
EyeTime now;
startTime = now.GetTimeMs();
#ifdef __APPLE__
aslClientHandle = 0;
#endif
}
EyeLog::~EyeLog()
{
if (auditKeyring) {
delete auditKeyring;
auditKeyring = nullptr;
}
}
#ifdef LOG_TO_SYSLOG
// Safe version that truncates messages if too long, and no varargs
void EyeLog::SafeSyslog (int priority, std::string msg)
{
if (msg.size() >= 256) {
std::string smsg (msg, 0, 252);
smsg += "...";
syslog (priority, "%s", smsg.c_str());
}
else
syslog (priority, "%s", msg.c_str());
}
#endif
bool EyeLog::BeginAudit (const std::string& auditorPublicKey, const std::string& auditCacheDir /*, uploadcallback() */)
{
Key pk ("PK", "auditor", "", Key::GENERIC);
pk.Import (auditorPublicKey.c_str(), auditorPublicKey.size(), NULL, Key::PEM);
if (pk.GetType() == Key::GENERIC) {
IONUERROR ("EyeLog::BeginAudit(): Invalid auditor key");
return false;
}
bool rc = BeginAudit (pk, auditCacheDir);
return rc;
}
bool EyeLog::BeginAudit (const Key& auditorPublicKey, const std::string& auditCacheDir /*, uploadcallback() */)
{
auditCachePath = auditCacheDir;
if (auditCachePath.size() > 0 && auditCachePath[auditCachePath.size()-1] != '/')
auditCachePath += '/';
if (clientId.size() == 0)
clientId = "unknown";
size_t bytes = 0;
unsigned char* buff = NULL;
if (auditorPublicKey.GetType() == Key::RSA_PUBLIC || auditorPublicKey.GetType() == Key::RSA) {
if (! auditKeyring->GenerateAESHMACKey ("hmkey", "session"))
return false;
else
auditKey = const_cast<Key*>(auditKeyring->GetKey ("hmkey"));
}
else if (auditorPublicKey.GetType() == Key::EC_PUBLIC || auditorPublicKey.GetType() == Key::EC) {
auditKeyring->GenerateECKey ("EC", "Ephemeral");
const Key* ekey = auditKeyring->GetKey ("EC");
buff = ekey->GenerateECDHSecret (&auditorPublicKey, &bytes);
auditKey = const_cast<Key*>(auditKeyring->GetKey ("hmkey"));
auditKey->SetKey (buff, bytes);
delete[] buff;
}
std::string audf = auditCachePath + clientId + ".ioa";
auditFile.open (audf, ios::out | ios::binary | ios::trunc);
if (!auditFile.is_open()) {
IONUERROR ("EyeLog::BeginAudit (%s) Unable to open audit file", audf.c_str());
return false;
}
// Initial starting sequence number with a random value
sequencelogic::RandomBytes (2, (unsigned char*)(&auditId));
// For RSA encrypt the random AESHMAC key with public key
if (auditorPublicKey.GetType() == Key::RSA_PUBLIC || auditorPublicKey.GetType() == Key::RSA) {
buff = auditorPublicKey.PublicKeyEncrypt (auditKey->GetKey(), SL_AES_KEY_LEN*2, &bytes);
char* b64 = new char [bytes * 4 / 3 + 4];
Base64Encode (buff, bytes, b64);
pthread_mutex_lock (&ionu_audit_mutex);
auditFile << b64 << endl;
pthread_mutex_unlock (&ionu_audit_mutex);
delete[] buff;
delete[] b64;
}
// For EC output the ephemeral public key
else {
char* ephemeral = auditKeyring->GetPubKey ("EC");
pthread_mutex_lock (&ionu_audit_mutex);
auditFile << ephemeral << endl;
pthread_mutex_unlock (&ionu_audit_mutex);
delete[] ephemeral;
}
Audit (AUDIT_START, deviceId);
return true;
}
#ifdef LOG_TO_UDP_514_LOCALHOST
struct CLoggerSocket
{
SOCKET sockfd;
struct sockaddr_in addr;
CLoggerSocket() : sockfd(INVALID_SOCKET)
{
WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA wsaData;
int err = ::WSAStartup (wVersionRequested, &wsaData);
if (err != 0) {
printf("WSAStartup failed with error: %d\n", err);
}
else if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
printf("Could not find version of Winsock.dll that supports 2.2\n");
::WSACleanup();
}
else {
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(514);
}
}
void Write(const char *buf, int len)
{
if (INVALID_SOCKET != sockfd) {
sendto(sockfd, buf, len, 0, (struct sockaddr *)&addr, sizeof(addr));
}
}
void Close()
{
if (INVALID_SOCKET != sockfd) {
closesocket (sockfd);
::WSACleanup();
sockfd = INVALID_SOCKET;
}
}
};
static CLoggerSocket loggerSocket;
#endif
#ifdef LOG_TO_UDP_514_LOCALHOST
static void udpPrintf(const char *fmt, ...)
{
char buf[1280];
va_list marker;
va_start(marker, fmt);
int n = vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, marker);
va_end(marker);
n = (n < 0) ? (sizeof(buf) -1) : n;
loggerSocket.Write(buf, n);
}
#endif
#ifdef LOG_TO_UDP_514_LOCALHOST
static const char* levStr(sequencelogic::EyeLog::LOG_LEVEL level)
{
switch (level)
{
case sequencelogic::EyeLog::IONU_AUDIT: return "AUD";
case sequencelogic::EyeLog::IONU_FATAL: return "FAT";
case sequencelogic::EyeLog::IONU_ERROR: return "ERR";
case sequencelogic::EyeLog::IONU_WARN: return "WRN";
case sequencelogic::EyeLog::IONU_INFO: return "INF";
case sequencelogic::EyeLog::IONU_DEBUG: return "DBG";
case sequencelogic::EyeLog::IONU_TRACE: return "TRA";
default: return "UNK";
}
}
#endif
bool EyeLog::Log (LOG_LEVEL level, string& msg)
{
bool rc = true;
// copy all log messages to UDP localhost:514 regardless of filter level
// this supports a trace log viewer tool for developers
#ifdef LOG_TO_UDP_514_LOCALHOST
udpPrintf("%.5X %.5X %s::%s", GetCurrentProcessId(), GetCurrentThreadId(), levStr(level), msg.c_str());
#endif
if (logLevel >= level) {
std::string logmsg;
std::stringstream etimestr;
std::string etime;
EyeTime now;
#ifdef LOG_USE_VA_STYLE
// VS 2012 does not support variadic templates so use the old style message
logmsg = _logLevels[level];
logmsg += ":";
logmsg += clientId.c_str();
logmsg += "[" ;
logmsg += now.GetISO8601Time() ;
logmsg += "] ";
logmsg += msg.c_str();
#else
const char* args[4];
for (int i = 0; i < 4; ++i) {
switch (formatargs[i]) {
case IONU_LEVEL : args[i] = _logLevels[level]; break;
case IONU_ULEVEL : args[i] = _logLevelsU[level]; break;
case IONU_CLIENT : args[i] = clientId.c_str(); break;
case IONU_ETIME : etimestr << (now.GetTimeMs() - startTime); etime = etimestr.str(); args[i] = etime.c_str(); break;
case IONU_TIME : args[i] = now.GetISO8601Time(); break;
case IONU_MESSAGE : args[i] = msg.c_str(); break;
case IONU_TERMINAL: args[i] = ""; break;
}
}
logsprintf (logmsg, format.c_str(), args[0], args[1], args[2], args[3]);
#endif
pthread_mutex_lock (&ionu_logger_mutex);
if (logFile.is_open()) {
if (logFile.bad() || logFileSize + logmsg.size() + SL_ENDL_LEN > maxLogFileSize) { // Roll the log files
logFile.close();
std::stringstream logfile;
std::string current;
std::string next;
logfile << logFilename << "." << maxLogFiles - 1;
next = logfile.str();
for (int i = maxLogFiles -2; i >= 0; --i) {
logfile.str("");
if (i > 0) {
logfile << logFilename << "." << i;
current = logfile.str();
}
else
current = logFilename;
struct stat buf;
if (stat (current.c_str(), &buf) != -1) {
if (!sequencelogic::RenameFile (current, next)) {
std::string logerr ("EyeLog::Log (");
logerr += logmsg;
logerr += ") - Unable to roll logfile (";
logerr += current;
logerr += " to ";
logerr += next;
logerr += ")";
//cout << logerr << endl;
#ifdef LOG_TO_SYSLOG
SafeSyslog (LOG_ERR, logerr);
#endif
rc = false;
}
}
next = current;
}
if (rc) {
// Open logfile and truncate
logFile.open (logFilename, ios::out | ios::binary | ios::trunc);
if (!logFile.is_open()) {
std::string logerr ("EyeLog::Log (");
logerr += logmsg;
logerr += ") - Unable to open logfile (";
logerr += logFilename;
logerr += ")";
//cout << logerr << endl;
#ifdef LOG_TO_SYSLOG
SafeSyslog (LOG_ERR, logerr);
#endif
rc = false;
}
else {
if (deviceId.size() > 0) {
EyeTime now;
string header;
header = "[";
header += now.GetISO8601Time();
header += "] ";
header += deviceId;
logFile << header << endl;
logFileSize = header.size() + SL_ENDL_LEN;
}
else
logFileSize = 0;
}
}
}
if (rc) {
logFile << logmsg << endl;
logFileSize += logmsg.size() + SL_ENDL_LEN;
if (logFile.bad()) {
std::string logerr ("EyeLog::Log (");
logerr += logmsg;
logerr += ") - Error writing to logfile (";
logerr += logFilename;
logerr += ")";
//cout << logerr << endl;
#ifdef LOG_TO_SYSLOG
SafeSyslog (LOG_ERR, logerr);
#endif
rc = false;
}
}
}
if (localSink == IONU_CONSOLE)
cout << logmsg << endl;
else if (localSink == IONU_SYSLOG) {
#ifdef WIN32
if (sysLogFile.is_open())
sysLogFile << logmsg << endl;
#elif defined(LOG_TO_SYSLOG)
int priority = LOG_INFO;
switch (level){
case IONU_FATAL: priority = LOG_CRIT; break;
case IONU_ERROR: priority = LOG_ERR; break;
case IONU_WARN: priority = LOG_WARNING; break;
case IONU_INFO: priority = LOG_INFO; break;
case IONU_DEBUG:
case IONU_TRACE:
default: priority = LOG_DEBUG; break;
}
SafeSyslog (priority, msg);
#elif defined(ANDROID)
int priority = ANDROID_LOG_INFO;
switch (level){
case IONU_FATAL: priority = ANDROID_LOG_ERROR; break;
case IONU_ERROR: priority = ANDROID_LOG_ERROR; break;
case IONU_WARN: priority = ANDROID_LOG_WARN; break;
case IONU_INFO:
case IONU_DEBUG: priority = ANDROID_LOG_INFO; break;
case IONU_TRACE:
default: priority = ANDROID_LOG_VERBOSE; break;
}
__android_log_print (priority, clientId.c_str(), "%s", msg.c_str());
#elif defined(__APPLE__)
int priority = AIONU_LEVEL_INFO;
switch (level){
case IONU_FATAL: priority = AIONU_LEVEL_CRIT; break;
case IONU_ERROR: priority = AIONU_LEVEL_ERR; break;
case IONU_WARN: priority = AIONU_LEVEL_WARNING; break;
case IONU_INFO: priority = AIONU_LEVEL_INFO; break;
case IONU_DEBUG:
case IONU_TRACE:
default: priority = AIONU_LEVEL_DEBUG; break;
}
asl_log (aslClientHandle, NULL, priority, "[%s]|%s", _logLevels[level], msg.c_str());
#endif
}
pthread_mutex_unlock (&ionu_logger_mutex);
return rc;
}
return rc;
}
bool EyeLog::Audit (AUDIT_CODE code, string& msg)
{
bool rc = true;
if (!auditKey) {
IONUDEBUG ("EyeLog::Audit (%s) Audit not initialized", msg.c_str());
return false;
}
size_t bytes = 0;
string auditmsg;
stringstream auditstream;
EyeTime now;
if (clientId.size() > 0) {
auditmsg += ":";
auditmsg += clientId;
}
auditmsg += ":[";
auditmsg += now.GetISO8601Time();
auditmsg += "] ";
auditmsg += msg;
auditstream << auditId++ << ":" << code << auditmsg;
unsigned char* buff = auditKey->HMACEncrypt ((unsigned char*)(auditstream.str().data()), auditstream.str().size(), &bytes);
char* b64 = new char [bytes * 4 / 3 + 4];
Base64Encode (buff, bytes, b64);
pthread_mutex_lock (&ionu_audit_mutex);
auditFile << b64 << endl;
if (auditFile.bad()) {
std::string logerr ("EyeLog::Audit (");
logerr += auditmsg;
logerr += ") - Error writing to auditfile (";
logerr += auditCachePath + clientId + ".ioa";
logerr += ")";
//cout << logerr << endl;
#ifdef LOG_TO_SYSLOG
SafeSyslog (LOG_ERR, logerr);
#endif
rc = false;
}
pthread_mutex_unlock (&ionu_audit_mutex);
delete[] b64;
delete[] buff;
return rc;
}
// Get the audit key from an audit file (RSA is encrypted EC is generated)
bool EyeLog::GetAuditKey (const Key& pk, const std::string& auditFilename)
{
string line;
size_t bytes = 0;
unsigned char* buff;
unsigned char cbuff[1024];
if (getline (auditFile, line)) {
// Ephemeral public key, use ECDH to generate AES+HMAC key
if (line.compare (0, 10, "-----BEGIN") == 0 || line.compare (0, 3, "MII") == 0) {
string ephemeral = line;
ephemeral += "\n";
while (getline (auditFile, line)) {
ephemeral += line;
ephemeral += "\n";
if (line.compare (0, 8, "-----END") == 0) {
Key* ekey = new Key ("EC", "Ephemeral", "", Key::GENERIC);
ekey->Import (ephemeral.c_str(), ephemeral.size()+1, "", Key::PEM);
buff = pk.GenerateECDHSecret (ekey, &bytes);
auditKey->SetKey (buff, bytes);
delete ekey;
delete[] buff;
return true;
}
}
}
else {
bytes = Base64Decode (line.c_str(), cbuff);
buff = pk.PrivateKeyDecrypt (cbuff, bytes, &bytes);
if (buff && bytes == SL_AES_KEY_LEN*2) {
auditKey->SetKey (buff, bytes);
delete[] buff;
return true;
}
else {
IONUERROR ("EyeLog::GetAuditKey (%s): Invalid audit file, or wrong key", auditFilename.c_str());
return false;
}
}
}
IONUERROR ("EyeLog::GetAuditKey (%s): Invalid or empty audit file", auditFilename.c_str());
return false;
}
bool EyeLog::ReviewAudit (const std::string& auditorPrivateKey, const std::string& auditFilename)
{
// Check for garbage input
if (sequencelogic::StrLen (auditorPrivateKey.c_str(), 1024) == 0 || !sequencelogic::IsValidFilename (auditFilename)) {
IONUDEBUG ("EyeLog::ReviewAudit(): garbage input detected");
return false;
}
Key pk ("PK", "auditor", "", Key::GENERIC);
pk.Import (auditorPrivateKey.c_str(), auditorPrivateKey.size(), "", Key::PEM);
if (pk.GetType() != Key::RSA && pk.GetType() != Key::EC) {
IONUERROR ("EyeLog::ReviewAudit(): Invalid auditor key");
return false;
}
bool rc = ReviewAudit (pk, auditFilename);
return rc;
}
bool EyeLog::ReviewAudit (const Key& auditorPrivateKey, const std::string& auditFilename)
{
// Check for garbage input
if (!sequencelogic::IsValidFilename (auditFilename)) {
IONUDEBUG ("EyeLog::ReviewAudit(): garbage input detected");
return false;
}
if (auditFile.is_open())
auditFile.close();
auditFile.open (auditFilename, ios::in | ios::binary);
if (!auditFile.is_open()) {
IONUERROR ("EyeLog::ReviewAudit (%s): Unable to open audit file", auditFilename.c_str());
return false;
}
// Get the audit AES+HMAC key
if (!GetAuditKey (auditorPrivateKey, auditFilename)) {
return false;
}
bool rc = true;
size_t bytes = 0;
int errcnt = 0;
string line;
unsigned char* buff;
unsigned char cbuff[1024];
// Process the individual audit log entries
while (rc && auditFile >> line) {
bytes = Base64Decode (line.c_str(), cbuff);
buff = auditKey->HMACDecrypt (cbuff, bytes, &bytes);
if (buff) {
string msg ((char*)buff, bytes);
int id = 0;
size_t pos = 0;
for (; ; ++pos)
if (msg[pos] == ':')
break;
else
id = id * 10 + (msg[pos] - '0');
int code = 0;
pos++;
for (; ; ++pos)
if (msg[pos] == ':')
break;
else
code = code * 10 + (msg[pos] - '0');
// AUDIT_START = 0, get the starting random sequence number from this entry
if (code == 0)
auditId = id;
else if (id != auditId && errcnt <= IONU_MAX_AUDIT_ERRORS) {
IONUERROR ("Invalid sequence found, expecting %d found %d", auditId, id);
errcnt++;
}
cout << id << ":" << AuditCodeString (code) << msg.substr(pos) << endl;
delete[] buff;
}
else if (errcnt <= IONU_MAX_AUDIT_ERRORS) {
IONUERROR ("Invalid entry at position %d", auditId);
errcnt++;
}
if (errcnt == IONU_MAX_AUDIT_ERRORS) {
IONUERROR ("Error limit of %d reached, further errors suppressed", IONU_MAX_AUDIT_ERRORS);
rc = false;
}
auditId++;
}
// No errors and read all of the file successfully
if (errcnt > 0 || auditFile.bad())
rc = false;
auditFile.close();
return rc;
}
// Consolidate two audit files into one
bool EyeLog::ConsolidateAudit (const Key& auditorPrivateKey, const std::string& auditFile1, const std::string& auditFile2)
{
// Check for garbage input
if (auditorPrivateKey.GetType() != Key::RSA && auditorPrivateKey.GetType() != Key::EC) {
IONUERROR ("EyeLog::ConsolidateAudit(): Invalid auditor key");
return false;
}
if (!sequencelogic::IsValidFilename (auditFile1) || !sequencelogic::IsValidFilename (auditFile2)) {
IONUDEBUG ("EyeLog::ConsolidateAudit(): invalid audit file(s)");
return false;
}
if (auditFile.is_open())
auditFile.close();
auditFile.open (auditFile1, ios::in | ios::out | ios::binary);
if (!auditFile.is_open()) {
IONUERROR ("EyeLog::ConsolidateAudit (%s): Unable to open audit file", auditFile1.c_str());
return false;
}
std::fstream af2 (auditFile2, ios::in | ios::binary);
if (!af2.is_open()) {
IONUERROR ("EyeLog::ConsolidateAudit (%s): Unable to open audit file", auditFile2.c_str());
auditFile.close();
return false;
}
// Get the audit AES+HMAC key
if (!GetAuditKey (auditorPrivateKey, auditFile1)) {
IONUERROR ("EyeLog::ConsolidateAudit (%s): Unable to get AES+HMAC key", auditFile2.c_str());
auditFile.close();
af2.close();
return false;
}
bool rc = true;
int id = 0;
size_t bytes = 0;
size_t lineNum = 0;
int errcnt = 0;
string line;
unsigned char cbuff[1024];
unsigned char* back;
// Validate the first audit file
while (rc && auditFile >> line) {
bytes = Base64Decode (line.c_str(), cbuff);
back = auditKey->HMACDecrypt (cbuff, bytes, &bytes);
if (bytes == 0 || back == NULL) {
IONUERROR ("EyeLog::ConsolidateAudit (%s) Invalid entry at position %d", auditFile1.c_str(), (int)lineNum);
}
else {
string msg ((char*)back, bytes);
int id = 0;
size_t pos = 0;
for (; ; ++pos)
if (msg[pos] == ':')
break;
else
id = id * 10 + (msg[pos] - '0');
int code = 0;
pos++;
for (; ; ++pos)
if (msg[pos] == ':')
break;
else
code = code * 10 + (msg[pos] - '0');
// AUDIT_START = 0, get the starting random sequence number from this entry
if (code == 0)
auditId = id;
else
auditId++;
if (id != auditId && errcnt <= IONU_MAX_AUDIT_ERRORS) {
IONUERROR ("EyeLog::ConsolidateAudit (%s) Invalid sequence at %d, expecting %d found %d", auditFile1.c_str(), (int)lineNum, auditId, id);
cout << msg << endl;
errcnt++;
}
delete[] back;
}
}
// No errors and read all of the file successfully
if (errcnt > 0 || auditFile.bad())
rc = false;
auditFile.clear(); // Clear EOF flag to allow append
// Validate and append the second file, recrypt with first audit file HMAC key and resequence as needed
lineNum = 0;
Key* af2Key = NULL;
while (rc && af2 >> line) {
// Check the first line, could be either the key or just an audit message
if (lineNum == 0) {
if (line.compare (0, 10, "-----BEGIN") == 0 || line.compare (0, 3, "MII") == 0) {
string ephemeral = line;
while (auditFile >> line) {
ephemeral += line;
if (line.compare (0, 8, "-----END") == 0) {
Key* ekey = new Key ("EC", "Ephemeral", "", Key::GENERIC);
ekey->Import (ephemeral.c_str(), ephemeral.size()+1, "", Key::PEM);
back = auditorPrivateKey.GenerateECDHSecret (ekey, &bytes);
auditKey = new Key ("hmkey", "session", bytes, back, Key::AESHMAC);
delete[] back;
}
}
}
else {
bytes = Base64Decode (line.c_str(), cbuff);
back = auditorPrivateKey.PrivateKeyDecrypt (cbuff, bytes, &bytes);
if (back) {
af2Key = new Key ("hmkey", "", 2*SL_AES_KEY_LEN, back, Key::AESHMAC);
delete[] back;
}
else {
unsigned char* back = auditKey->HMACDecrypt (cbuff, bytes, &bytes);
if (bytes == 0 || back == NULL) {
IONUERROR ("Invalid entry in %s at position %d", auditFile2.c_str(), (int)lineNum);
errcnt++;
}
else {
string msg ((char*)back, bytes);
delete[] back;
for (int i = 0; ; ++i) {
if (msg[i] == ':')
break;
else
id = id * 10 + (msg[i] - '0');
}
if (id != auditId) {
IONUERROR ("Invalid sequence in %s, expecting %d found %d", auditFile2.c_str(), auditId, id);
cout << msg << endl;
errcnt++;
}
else // All good, just append
auditFile << line << endl;
auditId++;
}
}
}
}
// Append file is just audit messages, validation of first has already occured above
else if (af2Key == NULL) {
auditFile << line << endl;
}
// Different keys, so decrypt, resequence and encrypt with first key
else {
back = af2Key->HMACDecrypt (cbuff, bytes, &bytes);
if (bytes == 0 || back == NULL) {
IONUERROR ("Invalid entry in append file at position %d", lineNum);
errcnt++;
}
else {
string msg ((char*)back, bytes);
size_t found = msg.find (':');
if (found != std::string::npos) {
stringstream a;
a << auditId++ << msg.substr (found);
unsigned char* buff = auditKey->HMACEncrypt ((unsigned char*)(a.str().data()), a.str().size(), &bytes);
char* b64 = new char [bytes * 4 / 3 + 4];
Base64Encode (buff, bytes, b64);
cout << "requenced line:" << a.str() << endl;;
auditFile << b64 << endl;
delete[] buff;
delete[] b64;
}
delete[] back;
}
}
lineNum++;
}
// No errors and read all of the file successfully
if (errcnt > 0 || auditFile.bad() || af2.bad())
rc = false;
auditFile.close();
af2.close();
if (af2Key) delete af2Key;
return rc;
}