// Copyright (c) 2014-2015 IONU Security, Inc. All rights reserved. // // Logging and auditing services #ifndef WIN32 #include #endif #ifdef __APPLE__ #include "TargetConditionals.h" #endif #ifdef ANDROID #include #endif #ifdef WIN32 #include #endif #include #include #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(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(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; }