// Copyright (c) 2013-2015 IONU Security, Inc. All rights reserved. // Copyright (c) 2016 Sequence Logic, Inc. All rights reserved. // // Utility functions and classes #include #include #include #include #include #include #ifdef WIN32 #include #include #include #include "zlib/win32/zconf.h" #ifdef WIN64 typedef __int64 ssize_t; #else typedef __int32 ssize_t; #endif #else #include #include #endif #ifdef LIN64 #include #endif #include #include #include #include #include "zlib/zlib.h" #include "eyeconstants.h" #include "eyejson.h" #include "eyelog.h" #include "eyelock.h" #ifndef WIN32 #include "eyemimemap.h" #endif #include "eyetime.h" #include "eyeutils.h" using namespace std; using namespace sequencelogic; //static unsigned char SL_KEY_SALT[] = {'I', 's', 'O', 'a', 'n', 'l', 'U', 't'}; static char SL_KEY_SALT[] = "49734f616e6c5574"; #define SL_KEY_SALT_LEN 8 #define SL_CHUNK_SIZE 4096 // Chunk size for file read/0write unsigned char* sequencelogic::New (size_t bytes) { unsigned char* data = nullptr; #ifdef ANDROID data = new unsigned char[bytes]; if (!data) return nullptr; #else try { data = new unsigned char[bytes]; } catch (std::bad_alloc) { IONUDEBUG ("sequencelogic::New() - unable to allocate %d bytes", bytes); return nullptr; } #endif return data; } // Strips the path from a filename std::string sequencelogic::StripPath (const std::string& filename) { const size_t filename_idx = filename.find_last_of("\\/"); std::string entryname; if (std::string::npos != filename_idx) entryname = filename.substr (filename_idx + 1, filename.size() - filename_idx - 1); else entryname = filename; return entryname; } std::string sequencelogic::Canonicalise (const std::string& path, bool lowercase) #ifdef WIN32 // Code adapted from http://pdh11.blogspot.com/2009/05/pathcanonicalize-versus-what-it-says-on.html { std::wstring utf16 = sequencelogic::toWString(path); wchar_t canon[MAX_PATH]; /** Note that PathCanonicalize does NOT do what we want here -- it's a * purely textual operation that eliminates /./ and /../ only. */ DWORD rc = ::GetFullPathNameW(utf16.c_str(), MAX_PATH, canon, NULL); if (!rc) { std::string utf8; utf8.resize(path.size()); std::transform(path.begin(), path.end(), utf8.begin(), ::tolower); std::replace(utf8.begin(), utf8.end(), '\\', '/'); return utf8; } utf16 = canon; if (utf16.length() >= 6) { /** Get rid of \\?\ and \\.\ prefixes on drive-letter paths */ if (!wcsncmp(utf16.c_str(), L"\\\\?\\", 4) && utf16[5] == L':') utf16.erase(0,4); else if (!wcsncmp(utf16.c_str(), L"\\\\.\\", 4) && utf16[5] == L':') utf16.erase(0,4); } if (utf16.length() >= 10) { /** Get rid of \\?\UNC on drive-letter and UNC paths */ if (!wcsncmp(utf16.c_str(), L"\\\\?\\UNC\\", 8)) { if (utf16[9] == L':' && utf16[10] == L'\\') utf16.erase(0,8); else { utf16.erase(0,7); utf16 = L"\\" + utf16; } } } /** Anything other than UNC and drive-letter is something we don't * understand */ if (utf16[0] == L'\\' && utf16[1] == L'\\') { if (utf16[2] == '?' || utf16[2] == '.') return path; // Not understood /** OK -- UNC */ } else if (((utf16[0] >= 'A' && utf16[0] <= 'Z') || (utf16[0] >= 'a' && utf16[0] <= 'z')) && utf16[1] == ':') { /** OK -- drive letter -- unwind subst'ing */ for (;;) { wchar_t drive[3]; drive[0] = (wchar_t)toupper(utf16[0]); drive[1] = L':'; drive[2] = L'\0'; canon[0] = L'\0'; rc = ::QueryDosDeviceW(drive, canon, MAX_PATH); if (!rc) break; if (!wcsncmp(canon, L"\\??\\", 4)) { utf16 = std::wstring(canon+4) + std::wstring(utf16, 2); } else // Not subst'd break; } wchar_t drive[4]; drive[0] = (wchar_t)toupper(utf16[0]); drive[1] = ':'; drive[2] = '\\'; drive[3] = '\0'; #ifdef LATER rc = ::GetDriveTypeW(drive); if (rc == DRIVE_REMOTE) { DWORD bufsize = MAX_PATH; /* QueryDosDevice and WNetGetConnection FORBID the * trailing slash; GetDriveType REQUIRES it. */ drive[2] = '\0'; rc = ::WNetGetConnectionW(drive, canon, &bufsize); if (rc == NO_ERROR) utf16 = wstring(canon) + std::wstring(utf16, 2); } #endif } else { // Not understood return path; } /** Canonicalise case and 8.3-ness */ rc = ::GetLongPathNameW(utf16.c_str(), canon, MAX_PATH); if (rc) utf16 = canon; std::string utf8 = sequencelogic::toAString(utf16.c_str()); std::replace(utf8.begin(), utf8.end(), '\\', '/'); if (lowercase) std::transform(utf8.begin(), utf8.end(), utf8.begin(), ::tolower); return utf8; } #else { #ifdef LIN64 char* cpath = realpath (path.c_str(), nullptr); if (cpath) { std::string fullpath (cpath); free (cpath); return fullpath; } #endif return path; } #endif std::string sequencelogic::TempFilename (const std::string& filename, const std::string& suffix) { #ifdef WIN32 // If no suffix (e.g. .lk) then use Windows code to generate a unique temp filename in // local temp directory, avoids creation on flash drive or other slow external drive if (suffix.size() == 0) { TCHAR lpTempPathBuffer[MAX_PATH]; TCHAR szTempFileName[MAX_PATH]; DWORD dwRetVal = GetTempPath(MAX_PATH, lpTempPathBuffer); if (dwRetVal <= MAX_PATH && dwRetVal > 0) { if (GetTempFileName(lpTempPathBuffer, TEXT("IONU"), 0, szTempFileName) != 0) { std::string tmpname(szTempFileName); return tmpname; } } } #endif std::string tmpname(filename); const size_t filename_idx = tmpname.find_last_of("\\/"); if (std::string::npos != filename_idx) { if (tmpname[filename_idx + 1] != '.') tmpname.insert(filename_idx + 1, "."); } else if (tmpname[0] != '.') tmpname.insert(0, "."); if (suffix.size() > 0) tmpname.append(suffix); else tmpname.append(".tmp"); return tmpname; } bool sequencelogic::Compress (unsigned char* dest, size_t* dest_len, const unsigned char* src, size_t src_len) { if (!dest || !src || src_len == 0) return false; int rc = Z_OK; uLong dlen = (uLong)(*dest_len); rc = compress (dest, &dlen, src, (uLong)src_len); *dest_len = (size_t)dlen; if (rc == Z_OK) { //IONUDEBUG ("sequencelogic::Compress() - %d bytes in, %d out", (int)src_len, (int)*dest_len); return true; } else { return false; } } bool sequencelogic::Uncompress (unsigned char* dest, size_t* dest_len, const unsigned char* src, size_t src_len) { if (!dest || !src || src_len == 0) return false; int rc = Z_OK; uLong dlen = (uLong)(*dest_len); rc = uncompress (dest, &dlen, src, (uLong)src_len); *dest_len = (size_t)dlen; if (rc == Z_OK) { //IONUDEBUG ("sequencelogic::Uncompress() - %d bytes in, %d out", (int)src_len, (int)*dest_len); return true; } else { return false; } } // Convert an integer value to a string of octal digits with leading 0's. bool sequencelogic::IntToOctal (size_t value, char* buffer, size_t digits) { if (!buffer) return false; char* ptr = buffer; size_t i; for (i = 0; i < digits; ++i) *ptr++ = '0'; *ptr = '\0'; while (value > 0) { *--ptr = (char) (value & 0x07) + '0'; value >>= 3; } return true; } // Convert an Octal string to an integer value size_t sequencelogic::OctalToInt (const char* value, size_t len) { if (!value) return 0; size_t output = 0; while (*value && len > 0) { unsigned char digit = *value; if (digit != ' ' && digit != '\0') { output = output*8 + (digit - '0'); } value++; len--; } return output; } static const char* lut = "0123456789abcdef"; // Convert binary data to hex string, caller has allocated len*2 + 1 for hex; bool sequencelogic::BinaryToHex (const unsigned char *data, size_t len, char* hex) { if (!hex) return false; if (!data || len == 0) { *hex = '\0'; return false; } char* p = hex; for (size_t i = 0; i < len; ++i) { const unsigned char c = data[i]; *p++ = lut[c >> 4]; // High order nyble *p++ = lut[c & 15]; // Low order nyble } *p = '\0'; return true; } // Returns numeric value of hex digit unsigned char sequencelogic::HexDigitToValue (const char digit) { unsigned char value = 255; if (digit >= '0' && digit <= '9') { value = digit - '0'; } else if (digit >= 'a' && digit <= 'f') { value = 10 + digit - 'a'; } else if (digit >= 'A' && digit <= 'F') { value = 10 + digit - 'A'; } else { IONUDEBUG("HexDigitToValue: invalid hex digit <%c>", digit); } return value; } // Convert hex string to binary data, caller has allocated strlen()/2 for data bool sequencelogic::HexToBinary (const char* hex, unsigned char *data) { if (!hex || !data) return false; size_t len = strlen (hex); return sequencelogic::HexToBinary (hex, len, data); } // Convert hex string to binary data, caller has allocated len/2 for data bool sequencelogic::HexToBinary (const char* hex, size_t len, unsigned char *data) { if (!hex || !data) return false; // Is an even multiple of 2? if ((len & 0x01) != 0) { IONUDEBUG("sequencelogic::HexToBinary: one nyble shy of a byte <%s>", hex); return false; } len /= 2; size_t i, in =0; unsigned char h; for (i = 0; i < len; ++i) { h = HexDigitToValue (hex[in++]); if (h == 255) return false; data[i] = h << 4; h = HexDigitToValue (hex[in++]); if (h == 255) return false; data[i] += h; } return true; } // Base32 encoding lookup table static const char* base32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; /** * Encode binary data as base32, caller has allocated (5/3 * input_len + 2) for output * One or two '=' are used for padding as required * *@param input - binary data to be encoded *@param len - number of bytes to be encodes *@param output - base32 encoded string */ bool sequencelogic::Base32Encode (const unsigned char *input, size_t len, char *output) { if (!output) return false; if (!input || len == 0) { *output = '\0'; return false; } do { *output++ = base32[(input[0] & 0xF8) >> 3]; if (len == 1 ) { *output++ = base32[(input[0] & 0x07) << 2]; *output++ = '='; *output++ = '='; *output++ = '='; *output++ = '='; *output++ = '='; *output++ = '='; break; } *output++ = base32[((input[0] & 0x07) << 2) | (input[1] & 0xC0) >> 6]; if (len == 2 ) { *output++ = base32[(input[1] & 0x3E) >> 1]; *output++ = base32[(input[1] & 0x01) << 4]; *output++ = '='; *output++ = '='; *output++ = '='; *output++ = '='; break; } *output++ = base32[(input[1] & 0x3E) >> 1]; *output++ = base32[((input[1] & 0x01) << 4) | ((input[2] & 0xF0) >> 4)]; if (len == 3 ) { *output++ = base32[(input[2] & 0x0F) << 1]; *output++ = '='; *output++ = '='; *output++ = '='; break; } *output++ = base32[((input[2] & 0x0F) << 1) | ((input[3] & 0x80) >> 7)]; *output++ = base32[(input[3] & 0x7C) >> 2]; if (len == 4 ) { *output++ = base32[(input[3] & 0x03) << 3]; *output++ = '='; break; } *output++ = base32[((input[3] & 0x03) << 3) | ((input[4] & 0xE0) >> 5)]; *output++ = base32[input[4] & 0x1F]; input += 5; len -= 5; } while (len > 0); // Terminate the base32 string *output = '\0'; return true; } // Returns numeric value of base32 digit unsigned char Base32DigitToValue (const char digit) { unsigned char value = 0; if (digit >= 'A' && digit <= 'Z') { value = digit - 'A'; } else if (digit >= '2' && digit <= '7') { value = 26 + digit - '2'; } else if (digit != '=') { IONUDEBUG("Base32DigitToValue: invalid base32 digit <%c>", digit); } return value; } // Convert base32 string to binary data, caller has allocated strlen()/2 for data size_t sequencelogic::Base32Decode (const char* base32, unsigned char *data) { if (!base32 || !data) return 0; size_t len = strlen (base32); size_t i = 0, pos = 0; int digit = 0; unsigned char byte = 0; while (i < len) { if (base32[i] == '=') { ++i; continue; } // unsigned char bb = Base32DigitToValue(base32[i]); // sequencelogic::DumpAsBinary (&bb, 1); switch (digit) { case 0 : byte = Base32DigitToValue (base32[i++]) << 3; break; case 1 : byte |= Base32DigitToValue (base32[i]) >> 2; data[pos++] = byte; byte = Base32DigitToValue (base32[i++]) << 6; break; case 2 : byte |= Base32DigitToValue (base32[i++]) << 1; break; case 3 : byte |= Base32DigitToValue (base32[i]) >> 4; data[pos++] = byte; byte = Base32DigitToValue (base32[i++]) << 4; break; case 4 : byte |= Base32DigitToValue (base32[i]) >> 1; data[pos++] = byte; byte = Base32DigitToValue (base32[i++]) << 7; break; case 5 : byte |= Base32DigitToValue (base32[i++]) << 2; break; case 6 : byte |= Base32DigitToValue (base32[i]) >> 3; data[pos++] = byte; byte = Base32DigitToValue (base32[i++]) << 5; break; case 7 : byte |= Base32DigitToValue (base32[i++]); data[pos++] = byte; digit = -1; break; } digit++; } return pos; } // Base64 encoding lookup table static const char* base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /** * Encode binary data as base64, caller has allocated (4/3 * input_len + 4) for output * One or two '=' are used for padding as required * *@param input - binary data to be encoded *@param len - number of bytes to be encodes *@param output - base64 encoded string */ bool sequencelogic::Base64Encode (const unsigned char *input, size_t len, char *output ) { if (!output) return false; if (!input || len == 0) { *output = '\0'; return false; } do { *output++ = base64[(input[0] & 0xFC) >> 2]; if ( len == 1 ) { *output++ = base64[((input[0] & 0x03) << 4)]; *output++ = '='; *output++ = '='; break; } *output++ = base64[((input[0] & 0x03) << 4) | ((input[1] & 0xF0) >> 4)]; if ( len == 2 ) { *output++ = base64[((input[1] & 0x0F) << 2)]; *output++ = '='; break; } *output++ = base64[((input[1] & 0x0F) << 2 ) | ((input[2] & 0xC0) >> 6)]; *output++ = base64[(input[2] & 0x3F)]; input += 3; } while (len -= 3); // Terminate the base64 string *output = '\0'; return true; } // Base64 decoding lookup table, base64url '-' and '_' are equivalent to '+' and '/' when decoding static int unbase64[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1 }; /** * Decode base64 string to binary, caller has allocated (3/4 * input_len) for output * One or two '=' are used for padding as required * *@param input - base64 encoded string *@param output - decoded binary data *@returns len - number of bytes in binary data, -1 if error */ size_t sequencelogic::Base64Decode (const char *input, unsigned char *output) { if (!input || !output) return 0; size_t len = strlen (input); if (len ==0 ) return 0; size_t out_len = 0; size_t i = 0; char *base64 = new char [len + 1]; if (!base64) return out_len; char *b64 = base64; // Check for invalid characters and remove any new lines or extraneous backslashes while (*input) { char c = *input++; if (c == '\n' || c == '\\') { len--; } else if ((c & 0x80) != 0 || unbase64[(int)c] == -1) { IONUDEBUG ("invalid character for base64 encoding: <%c>", c); delete[] base64; return 0; } else { b64[i++] = c; } } b64[i] = '\0'; // Is an even multiple of 4 if (len < 4 || (len & 0x03) != 0) { delete[] base64; return 0; } do { *output++ = (unsigned char)unbase64[(int)b64[0]] << 2 | (unbase64[(int)b64[1]] & 0x30) >> 4; out_len++; if (b64[2] != '=') { *output++ = (unbase64[(int)b64[1]] & 0x0F) << 4 | (unbase64[(int)b64[2]] & 0x3C) >> 2; out_len++; } if (b64[3] != '=') { *output++ = (unbase64[(int)b64[2]] & 0x03) << 6 | (unsigned char)unbase64[(int)b64[3]]; out_len++; } b64 += 4; } while (len -= 4); delete [] base64; return out_len; } // Format a time_t struct as ISO 8601 formatted string (20 characters) std::string sequencelogic::GetISO8601Time (time_t t) { struct tm tm; #ifdef WIN32 ::gmtime_s(&tm, &t); #else gmtime_r(&t, &tm); #endif char isodate[32]; sprintf(isodate, "%04d-%02d-%02dT%02d:%02d:%02dZ", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); std::string datestr (isodate); return datestr; } // Get the current time as a long in milliseconds for timing and performance work long sequencelogic::GetSystemTime () { long ms; #ifdef WIN32 FILETIME ft; LARGE_INTEGER li; /* Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it * to a LARGE_INTEGER structure. */ GetSystemTimeAsFileTime (&ft); li.LowPart = ft.dwLowDateTime; li.HighPart = ft.dwHighDateTime; li.QuadPart /= 10000; // 100 nano seconds (10^-7) to milli seconds (10^-3) li.QuadPart -= 11644473600000l; // Convert from file time to UNIX epoch time ms = (long)li.QuadPart; #else struct timeval t; gettimeofday (&t, 0); ms = t.tv_sec * 1000 + t.tv_usec / 1000; #endif return ms; } // Merge 2 half keys using xor to create a new key (works with any number of bytes) bool sequencelogic::XorKeys (const unsigned char* k1, const unsigned char* k2, unsigned char* ok, size_t len) { if (!k1 || !k2 || !ok || len == 0) return false; for (size_t i = 0; i < len; ++i) { *ok++ = *k1++ ^ *k2++; } return true; } // sequencelogic::StrDup implementation using new[]delete memory allocation, exception handling and NULL check char* sequencelogic::StrDup (const char* str) { // Check for NULL, allow empty string, check for binary char* dup = NULL; if (str && (*str == '\0' || sequencelogic::StrLen (str, 8) > 0)) { size_t len = strlen (str); #ifdef ANDROID dup = new char [len + 1]; if (!dup) return NULL; #else try { dup = new char [len + 1]; } catch (std::bad_alloc) { IONUDEBUG ("sequencelogic::StrDup() - unable to allocate %d bytes", static_cast(len)); return NULL; } #endif memcpy (dup, str, len + 1); } return dup; } // StrCmp implementation with pointer checking int sequencelogic::StrCmp (const char* str1, const char* str2) { int rc = 0; if (str1 == str2 ) rc = 0; else if (!str1) rc = -1; else if (!str2) rc = 1; else rc = strcmp (str1, str2); return rc; } // StrnCmp implementation with pointer checking int sequencelogic::StrnCmp (const char* str1, const char* str2, size_t len) { int rc = 0; if (str1 == str2 || len == 0) rc = 0; else if (!str1) rc = -1; else if (!str2) rc = 1; else rc = strncmp (str1, str2, len); return rc; } // StrCpy implementation with pointer checking void sequencelogic::StrCpy (char* dst, const char* src) { if (dst && src) // Both strings are "ok" strcpy (dst, src); else if (dst && !src) // Source string is NULL *dst = '\0'; } // StrCat implementation with pointer checking void sequencelogic::StrCat (char* dst, const char* src) { if (dst && src) // Both strings are "ok" strcat (dst, src); } // StrLen implementation with pointer, max length limit and isascii checking size_t sequencelogic::StrLen (const char* str, size_t max) { if (str) { for (size_t i = 0; i < max; ++i) { unsigned char c = static_cast(str[i]); if (c == '\0') return i; else if (c != '\n' && c != '\r' && c != '\t' && (c > 0x7f || c < 0x20)) return 0; } return max + 1; } return 0; } int sequencelogic::MemCmp (const void* mem1, const void* mem2, size_t len) { int rc = 0; if (mem1 == mem2 ) rc = 0; else if (!mem1) rc = -1; else if (!mem2) rc = 1; else rc = memcmp (mem1, mem2, len); return rc; } void sequencelogic::MemCpy (void* dst, const void* src, size_t len) { if (dst && src) memcpy (dst, src, len); } // Keep the compiler from optimizing this as dead code, as this is used to clear keys void sequencelogic::MemSet (volatile void* mem, unsigned char val, size_t len) { if (mem && len > 0) { volatile unsigned char* buf = static_cast(mem); while (len--) *buf++ = val; } } // Check string data to see if it contains any invalid ascii characters bool sequencelogic::IsStrAscii (const char* str, size_t len) { if (!str) return false; for (size_t i = 0; i < len; ++i) { unsigned char c = static_cast(str[i]); if (c == '\0' && i+1 >= len) // Null termination return true; else if (c != '\n' && c != '\r' && c != '\t' && (c > 0x7f || c < 0x20)) { //IONUDEBUG ("sequencelogic::IsStrAscii() - non-ascii character <%x> found at %d of %d", (int)c, (int)i, (int)len); return false; } } return true; } // Check string data to see if it contains any invalid UTF-8 encodings bool sequencelogic::IsStrUTF8 (const char* s, size_t len) { if (!s) return false; for (size_t i = 0; i < len; ++i) { unsigned char c = static_cast(s[i]); if (c <= 0x7f) // 1 byte encoding continue; else if ((c & 0xE0) == 0xC0) { // 2 byte encoding if (i+1 < len && (s[i+1] & 0xC0) == 0x80) ++i; else return false; } else if ((c & 0xF0) == 0xE0) { // 3 byte encoding if (i+2 < len && (s[i+1] & 0xC0) == 0x80 && (s[i+2] & 0xC0) == 0x80) i += 2; else return false; } else if ((c & 0xF8) == 0xF0) { // 4 byte encoding if (i+3 < len && (s[i+1] & 0xC0) == 0x80 && (s[i+2] & 0xC0) == 0x80 && (s[i+3] & 0xC0) == 0x80) i += 3; else return false; } else return false; } return true; } // Check a filename to help ensure that it is valid bool sequencelogic::IsValidFilename (const char* filename) { if (filename && *filename) { for (size_t i = 0; i < SL_MAX_PATH; ++i) { unsigned char c = static_cast(filename[i]); if (c == '\0' && i > 0) return true; else if (c > 0x7f || c < 0x20) return false; } return false; } return false; } bool sequencelogic::IsValidFilename (const std::string& filename) { if (filename.size() > 0 && filename.size() <= SL_MAX_PATH) { for (std::string::const_iterator itr = filename.begin(); itr != filename.end(); ++itr) { unsigned char c = static_cast(*itr); if (c == '\0' && itr != filename.begin()) return true; else if (c > 0x7f || c < 0x20) return false; } return true; } return false; } // Check a filename to ensure that it is valid and can be opened for read bool sequencelogic::CanReadFile (const char* filename) { if (sequencelogic::IsValidFilename (filename)) { ifstream file (filename, ios::in | ios::binary); if (file.is_open()) { file.close(); return true; } } return false; } // Check a filename to ensure that it is valid and can be opened for read bool sequencelogic::CanReadFile (const std::string& filename) { if (sequencelogic::IsValidFilename (filename)) { ifstream file (filename, ios::in | ios::binary); if (file.is_open()) { file.close(); return true; } } return false; } std::string sequencelogic::EncryptFilename (const std::string& filename, const Key& key) { if (key.GetType() == Key::AES) { size_t bytes = 0; unsigned char* edata = key.SymmetricEncryptBuffer ((const unsigned char*)filename.data(), filename.size(), &bytes, key.GetIv()); //size_t b64len = 4 * bytes / 3 + 4; char buff[512]; sequencelogic::Base64Encode (edata, bytes, buff); std::string b64 = buff; delete[] edata; return b64; } else return filename; } std::string sequencelogic::DecryptFilename (const std::string& filename, const Key& key) { if (key.GetType() == Key::AES) { size_t bytes = 0; unsigned char edata[512]; // 3 * filename.size() / 4; bytes = sequencelogic::Base64Decode (filename.data(), edata); char* fname = (char*)key.SymmetricDecryptBuffer (edata, bytes, &bytes, key.GetIv()); std::string b64 (fname, bytes); delete[] fname; return b64; } else return filename; } int sequencelogic::CompareFilenames (const std::string& f1, const std::string& f2) { #ifdef WIN32 if (f1.size() > f2.size()) return 1; else if (f1.size() < f2.size()) return -1; else { for (size_t i = 0; i < f1.size(); ++i) { unsigned char c1 = (unsigned char)(isalpha (f1[i]) ? toupper (f1[i]) : f1[i]); unsigned char c2 = (unsigned char)(isalpha (f2[i]) ? toupper (f2[i]) : f2[i]); if (c1 > c2) return 1; else if (c1 < c2) return -1; } return 0; } #else return f1.compare (f2); #endif } // Check a filename to ensure that it is valid and if the file exists in gzip format bool sequencelogic::IsGzipFile (const std::string& filename) { if (sequencelogic::IsValidFilename (filename)) { ifstream file (filename, ios::in | ios::binary); if (file.is_open()) { char header[8]; sequencelogic::MemSet (header, 0, 8); char hex[16]; file.read (header, 8); sequencelogic::BinaryToHex ((unsigned char*)header, 3, hex); file.close(); if (sequencelogic::StrnCmp (hex, SL_EYE_GZIP_HEADER, sizeof(SL_EYE_GZIP_HEADER)-1) == 0) { // gzip and deflate //IONUDEBUG ("%s is gzip", filename); return true; } } } return false; } bool sequencelogic::IsLockFile (const std::string& filename) { std::string ext = ""; size_t dot = filename.find_last_of("."); if (dot != std::string::npos && ++dot < filename.size()) ext = filename.substr(dot, filename.size() - dot); #ifdef WIN32 if (ext.size() == 2 && (ext.compare (0, 2, "lk") == 0 || ext.compare (0, 4, "LK") == 0)) #else if (ext.size() == 2 && ext.compare (0, 2, "lk") == 0) #endif return true; else return false; } bool sequencelogic::CopyFiles (const std::string& s, const std::string& d) { ifstream src (s, ios::binary); if (! src.is_open()) return false; ofstream dst (d, ios::binary); if (! dst.is_open()) { src.close(); return false; } dst << src.rdbuf(); src.close(); dst.close(); if (dst.fail()) { sequencelogic::RemoveFile (d); return false; } else return true; } bool sequencelogic::RenameFile (const std::string& src, const std::string& dst) { // Check if the case insensitive names are the same if (sequencelogic::CompareFilenames (src, dst) == 0) return true; // Remove any old locks sequencelogic::ReleaseEyeLock (src); bool okay = true; #ifdef WIN32 // There are times when Dropbox or other sync programs are syncing the file behind us. Retry a few times. for (int i = 0; !(okay = !!MoveFileEx(src.c_str(), dst.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) && i < 3; i++) Sleep(100 + i*200); // we *really* don't want to fail! #else //remove (dst.c_str()); // May be needed on non-Posix systems, failure expected when doesn't exist so don't check okay = (rename (src.c_str(), dst.c_str()) == 0); #endif if (!okay) { IONUDEBUG ("sequencelogic::RenameFile(%s, %s) failed: %s", src.c_str(), dst.c_str(), sequencelogic::GetLastErrorMessage()); return false; } return true; } bool sequencelogic::RemoveFile (const std::string& src) { // Remove any old locks sequencelogic::ReleaseEyeLock (src); #ifdef WIN32 if (!DeleteFile (src.c_str())) { #else if (remove (src.c_str()) != 0) { #endif if (!sequencelogic::IsLockFile (src)) { IONUDEBUG ("sequencelogic::RemoveFile(%s) failed: %s", src.c_str(), sequencelogic::GetLastErrorMessage()); } return false; } return true; } // Split a string into tokens based on separator std::vector sequencelogic::SplitString (const std::string& value, const char separator) { std::vector tokens; std::string::size_type pos = 0; std::string::size_type end = value.find (separator, pos); while (end != std::string::npos) { tokens.push_back (value.substr (pos, end - pos)); pos = end + 1; end = value.find (separator, pos); } // Get the last token or return the string if no separators found if (pos <= value.size()) tokens.push_back (value.substr (pos)); return tokens; } void sequencelogic::ReplaceStringInSitu(std::string& target, const std::string& search, const std::string& replace) { size_t pos = 0; while ((pos = target.find(search, pos)) != std::string::npos) { target.replace(pos, search.length(), replace); pos += replace.length(); } } // Check to ensure the urn is valid // urn:sl:VAN:PMO:device:document bool sequencelogic::IsValidURN (const char* urn) { EyeURN* myurn = new EyeURN (urn); bool rc = myurn->IsValid(); delete myurn; return rc; } char* EyeURN::GetURN() { char urn[SL_URN_MAX_LEN + 1]; strcpy (urn, SL_URN_PREFIX); strcat (urn, _van); strcat (urn, ":"); strcat (urn, _pmo); strcat (urn, ":"); strcat (urn, _dev); strcat (urn, ":"); strcat (urn, _doc); size_t len = strlen (urn) + 1; char* u = new char [len]; memcpy (u, urn, len); return u; } EyeURN::EyeURN (const char* urn) { if (sequencelogic::StrLen (urn, SL_URN_MAX_LEN) < SL_URN_MIN_LEN || strncmp (urn, SL_URN_PREFIX, 9) != 0) { IONUDEBUG ("URN: invalid urn"); _isValid = false; } else { _isValid = true; int digits = 0; URN_FIELD field = VAN; const char* ptr = urn + 9; // skip "urn:sl:" char* fptr = _van; while (*ptr && _isValid) { char c = *ptr++; if (c == ':') { *fptr = '\0'; digits = 0; if (field == VAN) { field = PMO; fptr = _pmo; } else if (field == PMO) { field = DEV; fptr = _dev; } else if (field == DEV) { field = DOC; fptr = _doc; } else { IONUDEBUG ("URN: invalid number of fields: %s", urn); _isValid = false; } } else if (!isxdigit (c)) { // Invalid character IONUDEBUG ("URN: invalid char <%c>: %s", c, urn); _isValid = false; } else { digits++; if (field == VAN && digits > SL_URN_VAN_LEN) { IONUDEBUG ("URN: VAN value out of range: %s", urn); _isValid = false; } else if (field == PMO && digits > SL_URN_PMO_LEN) { IONUDEBUG ("URN: PMO value out of range: %s", urn); _isValid = false; } else if (field == DEV && digits > SL_URN_DEV_LEN) { IONUDEBUG ("URN: DEV value out of range: %s", urn); _isValid = false; } else if (field == DOC && digits > SL_URN_DOC_LEN) { IONUDEBUG ("URN: DOC value out of range: %s", urn); _isValid = false; } else *fptr++ = c; } } *fptr = '\0'; // Check to see if right number of fields and range check values based on number of allowed digits if (_isValid && field != DOC) { IONUDEBUG ("URN: invalid number of fields: %s", urn); _isValid = false; } } } // Guess at Internet media type (MIME) if not specified, map is defined in "eyemimemap.h" const std::string sequencelogic::GuessMimeType (const std::string& filename) { std::string mime ("text/plain"); if (sequencelogic::IsValidFilename (filename)) { // If mimetype not specified, guess by extension std::string ext = ""; size_t dot = filename.find_last_of("."); if (dot != std::string::npos && ++dot < filename.size()) ext = filename.substr(dot, filename.size() - dot); if (ext.size() > 1) { #ifndef WIN32 auto search = sequencelogic::eyemimemap.find (ext); if (search != sequencelogic::eyemimemap.end ()) mime = search->second; #else std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); if (ext.compare ("docx") == 0) mime = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; else if (ext.compare ("pptx") == 0) mime = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; else if (ext.compare ("xlsx") == 0) mime = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; else if (ext.compare (0, 3, "doc") == 0) mime = "application/vnd.ms-word"; else if (ext.compare ("db") == 0) mime = "application/x-sqlite3"; else if (ext.compare ("png") == 0) mime = "image/png"; else if (ext.compare ("gif") == 0) mime = "image/gif"; else if (ext.compare (0, 2, "gz") == 0) mime = "application/zip"; else if (ext.compare ("zip") == 0) mime = "application/zip"; else if (ext.compare ("bmp") == 0) mime = "image/bmp"; else if (ext.compare ("ico") == 0) mime = "image/x-icon"; else if (ext.compare ("pdf") == 0) mime = "application/pdf"; else if (ext.compare (0, 2, "jp") == 0) mime = "image/jpeg"; else if (ext.compare ("mp3") == 0) mime = "audio/mpeg"; else if (ext.compare ("ogg") == 0) mime = "audio/ogg"; else if (ext.compare ("mov") == 0 || ext.compare ("qt") == 0) mime = "video/quicktime"; else if (ext.compare ("html") == 0 || ext.compare ("htm") == 0) mime = "text/html"; else if (ext.compare (0, 2, "mp") == 0) mime = "video/mpeg"; else if (ext.compare (0, 2, "xl") == 0) mime = "application/vnd.ms-excel"; else if (ext.compare (0, 2, "pp") == 0) mime = "application/vnd.ms-powerpoint"; else if (ext.compare ("json") == 0) mime = "application/json"; else if (ext.compare ("sequencelogic") == 0) mime = "application/ionu"; else if (ext.compare ("ionx") == 0) mime = "application/ionu-interchange"; else if (ext.compare ("vcf") == 0) mime = "text/x-vcard"; else if (ext.compare ("pgp") == 0) mime = "application/pgp-keys"; #endif else { // Read in the first 32 bytes of the file and do some checking ifstream file (filename, ios::in | ios::binary); if (file.is_open()) { char buff[32]; file.read (buff, 32); buff[31] = '\0'; if (! sequencelogic::IsStrAscii (buff, 31)) mime = "application/octet-stream"; // Binary else if (strstr (buff, "")) mime = "text/html"; else if (strstr (buff, "(len))) { IONUDEBUG ("EVP_DecryptUpdate failed for %d bytes", (int)len); } if (!EVP_DecryptFinal_ex (ctx, plaintext + bytes, &fbytes)) { IONUDEBUG ("EVP_DecryptFinal_ex failed"); } return bytes + fbytes; } size_t sequencelogic::Encryptor (EVP_CIPHER_CTX* ctx, size_t len, const unsigned char* plaintext, unsigned char* ciphertext, const unsigned char* key, const unsigned char* iv) { int bytes = 0; int fbytes = 0; EVP_EncryptInit_ex (ctx, EVP_aes_256_cbc(), NULL, key, iv); if (!EVP_EncryptUpdate (ctx, ciphertext, &bytes, plaintext, static_cast(len))) { IONUDEBUG ("EVP_EncryptUpdate failed for %d bytes", (int)len); } if (!EVP_EncryptFinal_ex (ctx, ciphertext + bytes, &fbytes)) { IONUDEBUG ("EVP_EncryptFinal_ex failed"); } return bytes + fbytes; } // Get a pointer to the function that implements the named message digest const EVP_MD* sequencelogic::GetMessageDigester (const char *digestName) { const EVP_MD* md = EVP_get_digestbyname(digestName); if (!md) { // In case init has not been called if (strcmp (MESSAGE_DIGEST_MD5, digestName) == 0) { md = EVP_md5(); } else if (strcmp (MESSAGE_DIGEST_SHA1, digestName) == 0) { md = EVP_sha1(); } else if (strcmp (MESSAGE_DIGEST_SHA224, digestName) == 0) { md = EVP_sha224(); } else if (strcmp (MESSAGE_DIGEST_SHA256, digestName) == 0) { md = EVP_sha256(); } else if (strcmp (MESSAGE_DIGEST_SHA384, digestName) == 0) { md = EVP_sha384(); } else if (strcmp (MESSAGE_DIGEST_SHA512, digestName) == 0) { md = EVP_sha512(); } } if (!md) { IONUERROR ("Cannot get digest with name %s", digestName); return NULL; } return md; } // Get a pointer to the function that implements the named cipher const EVP_CIPHER* sequencelogic::GetCipher (const char *cipherName) { const EVP_CIPHER* cipher = EVP_get_cipherbyname(cipherName); if (!cipher) { // In case init has not been called if (strcmp ("aes128cbc", cipherName) == 0) { cipher = EVP_aes_128_cbc(); } else if (strcmp ("aes256cbc", cipherName) == 0) { cipher = EVP_aes_256_cbc(); } #ifndef ANDROID else if (strcmp ("aes256gcm", cipherName) == 0) { cipher = EVP_aes_256_gcm(); } #endif } if (!cipher) { IONUERROR ("Cannot get cipher with name %s", cipherName); return NULL; } return cipher; } /** * Generic message digest function (in memory), works with any supported algorithm * @param digest - name of the message digest algorithm (e.g. md5, sha1, sha256...) * @param msg - message to digest * @param len - bytes in message * @returns - hex string containing digest */ std::string sequencelogic::DigestMessage (const char* digest, const unsigned char* msg, size_t len) { std::string hex = ""; // Check for garbage input if (!digest || !msg || len == 0) return hex; unsigned char md_value[EVP_MAX_MD_SIZE]; unsigned int md_len; EVP_MD_CTX *mdctx; const EVP_MD *md; md = sequencelogic::GetMessageDigester (digest); mdctx = EVP_MD_CTX_create(); EVP_DigestInit_ex (mdctx, md, NULL); EVP_DigestUpdate (mdctx, msg, len); EVP_DigestFinal_ex (mdctx, md_value, &md_len); EVP_MD_CTX_destroy (mdctx); char hexd[EVP_MAX_MD_SIZE * 2 + 1]; BinaryToHex (md_value, md_len, hexd); hex = hexd; return hex; } /** * Generic message digest function (file), works with any supported algorithm * @param digest - name of the message digest algorithm (e.g. md5, sha1, sha256...) * @param filename - readable and non-zero length file to digest * @returns - hex string containing digest */ std::string sequencelogic::DigestFile (const char* digest, const std::string& filename) { std::string hex = ""; // Check for garbage input if (!digest || !sequencelogic::IsValidFilename (filename)) return hex; unsigned char md_value[EVP_MAX_MD_SIZE]; unsigned int md_len; EVP_MD_CTX* mdctx; const EVP_MD* md; size_t bytes; md = GetMessageDigester (digest); ifstream file (filename, ios::in | ios::binary | ios::ate); if (file.is_open()) { // Get the file size and reset to the beginning bytes = (size_t)file.tellg(); file.seekg (0, ios::beg); if (bytes == 0) return hex; } else { return hex; } mdctx = EVP_MD_CTX_create(); EVP_DigestInit_ex (mdctx, md, NULL); // Process the file in chunks char* data = new char[SL_CHUNK_SIZE]; size_t len = (bytes > SL_CHUNK_SIZE ? SL_CHUNK_SIZE : bytes); do { file.read (data, len); if (file.fail()) { //SL_SEC_LOG (SL_WARN, "read failed: %d", errno); delete[] data; return hex; } else { EVP_DigestUpdate (mdctx, data, len); bytes -= len; len = (bytes > SL_CHUNK_SIZE ? SL_CHUNK_SIZE : bytes); } } while (bytes > 0); delete[] data; file.close(); // Finalize the message digest, convert to hex and return EVP_DigestFinal_ex (mdctx, md_value, &md_len); EVP_MD_CTX_destroy (mdctx); char hexd[EVP_MAX_MD_SIZE * 2 + 1]; BinaryToHex (md_value, md_len, hexd); hex = hexd; return hex; } /** * Compute the HMAC (message authentication code) using keyed SHA1 hash * @param key - key * @param klen - bytes of key * @param msg - message to digest * @param mlen - bytes in message * @returns - hex string containing HMAC */ std::string sequencelogic::HMACMessage (const unsigned char* key, size_t klen, const unsigned char* msg, size_t mlen) { std::string hex = ""; // Check for garbage input if (!key || !msg || klen == 0 || mlen == 0) return hex; unsigned char md_value[EVP_MAX_MD_SIZE]; unsigned int md_len; HMAC_CTX hmctx; HMAC_CTX_init (&hmctx); HMAC_Init (&hmctx, key, static_cast(klen), EVP_sha1()); HMAC_Update (&hmctx, msg, mlen); HMAC_Final (&hmctx, md_value, &md_len); HMAC_CTX_cleanup (&hmctx); HMAC_cleanup (&hmctx); char hexd[EVP_MAX_MD_SIZE * 2 + 1]; BinaryToHex (md_value, md_len, hexd); hex = hexd; return hex; } // Get the number of rounds used in key generation, more for shorter passwords // Number of rounds is 1000 - 100,000 int sequencelogic::GetRounds (const char* password) { if (password) return 2500; // This is the max we can expect from the JavaScript implementation ~20 seconds else return 0; #ifdef LATER double strength = PassWordStrength (password); int rounds = 10000; if (strength > 0.1) rounds = (int) (50.0 * ((8.0 / strength) * (8.0 / strength))); return rounds; #endif } // Algorithm to compute a relative password strength // @param clearPassword // @return 0.0 - 1.0 with 1.0 being the strongest password double sequencelogic::PassWordStrength (const char* password) { // Check for garbage input if (sequencelogic::StrLen (password, SL_PASSWORD_MAX_LEN) == 0) return 0.0; // Check for default and the worst passwords if (sequencelogic::StrnCmp (password, "password", 8) == 0 || sequencelogic::StrnCmp (password, "admin", 5) == 0 || sequencelogic::StrnCmp (password, "qwerty", 6) == 0) return 0.0; char c, pc = '\0', apc = '\0'; unsigned int upper, lower, digit, special, alphaSeq, digitSeq, numericSeq, specialSeq; upper = lower = digit = special = alphaSeq = digitSeq = numericSeq = specialSeq = 0; size_t len = sequencelogic::StrLen (password, 64); // Limit to 64 characters for (size_t i = 0; i < len; ++i) { c = password[i]; if (islower (c)) { lower++; if (c == pc || c == apc) alphaSeq++; pc = c; if (c == 'l') apc = '1'; else if (c == 'o') apc = '0'; else if (c == 'a') apc = '@'; } else if (isupper (c)) { if (i != 0) upper++; // Don't count when first character if (c == pc || c == apc) alphaSeq++; pc = (char)tolower (c); if (c == 'I') apc = '1'; else if (c == 'O') apc = '0'; else if (c == 'A') apc = '@'; } else if (isdigit (c)) { digit++; if (c == pc || c == apc) numericSeq++; else if (c + 1 == pc || c - 1 == pc) numericSeq++; else if (c - 1 == apc) numericSeq++; if (isdigit (pc)) digitSeq++; pc = c; if (c == '0') apc = 'o'; else if (c == '3') apc = 'e'; else if (c == '1') apc = 'l'; else apc = c; } else { if (i != len ) special++; // Don't count when last character if (c == pc) specialSeq++; pc = apc = c; if (c == '@') apc = 'A'; } } //printf ("len=%d upper=%d lower=%d dig=%d spcl=%d alphaSeq=%d numSeq=%d spclSeq=%d\n", // len, upper, lower, digit, special, alphaSeq, numericSeq, specialSeq); double strength = 0.0; if (digit >= 3 && digitSeq == digit - 1) len -= digitSeq; if (len >= 8 ) strength += (len > 12 ? 100.0 : len * 8.0); else strength += 35.0 - ((7 - len) * (7 - len)); if (lower && upper && digit && special) strength += 50.0; strength -= (alphaSeq + numericSeq + specialSeq) * 5.0; if (strength < 0.0 ) strength = 0.0; else if (strength > 100.0) strength = 1.0; else strength /= 100.0; return strength; } // Derive a key from the user password, can be updated to use bcrypt, scrypt or other in time // PBKDF2 (Password-Based Key Derivation Function 2) - also known as PKCS #5 v2.0 or RFC 2898 // Accepts MCF format $pbkdf2$2500$salt$keydata or base64 encoded raw keydata or "" for initial // Also Accepts $pbkdf2$10000$$ to create key with specific number of rounds and random salt // Returns "" for failure, valid MCF for validation (or creation) std::string sequencelogic::SlowHash (const std::string& password, const std::string& value) { std::string hash = ""; // Check for garbage input if (password.size() == 0) return hash; int rounds = 0; int saltSize = SL_AES_BLOCK_LEN; unsigned char salt[32]; char b64salt[64]; unsigned char skey[SL_AES_KEY_LEN]; char b64[128]; stringstream s; // Check if validation value exists in MCF, parse rounds and salt, validate password if (value.size() > 0 && value[0] == '$') { std::vector params = sequencelogic::SplitString (value, '$'); if (params.size() >= 4) { if (params[1].compare ("pbkdf2") != 0) { IONUDEBUG ("sequencelogic::SlowHash() - unknown algorithm identifier %s", params[1].c_str()); return hash; } rounds = atoi (params[2].c_str()); if (rounds < 2500) { IONUDEBUG ("sequencelogic::SlowHash() - number of rounds must be at least 2500 not %s", params[2].c_str()); return hash; } // No salt specified, get random bytes if (params[3].size() == 0) { if (RAND_bytes (salt, SL_AES_BLOCK_LEN) != 1) return hash; sequencelogic::Base64Encode (salt, SL_AES_BLOCK_LEN, b64salt); } else saltSize = (int)sequencelogic::Base64Decode (params[3].c_str(), salt); // Derive the key, validate or create MCF if (PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), (int)password.size(), salt, saltSize, rounds, SL_AES_KEY_LEN, skey) == 1 ) { sequencelogic::Base64Encode (skey, SL_AES_KEY_LEN, b64); // Not salt or key passed in so create new MCF if (params[3].size() == 0 || params[4].size() == 0) { s << "$pbkdf2$" << params[2] << "$" << b64salt << "$" << b64; return s.str(); } else if (params[4].compare (b64) != 0) return hash; else return value; } else return hash; } else return hash; } // Old style raw key data, 2500 rounds, fixed salt, validate password else if (value.size() > 0 && value[0] != '$') { unsigned char salt[8]; sequencelogic::HexToBinary (SL_KEY_SALT, salt); if (PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), (int)password.size(), salt, SL_KEY_SALT_LEN, 2500, SL_AES_KEY_LEN, skey) == 1 ) { sequencelogic::Base64Encode (skey, SL_AES_KEY_LEN, b64); // Password does not match if (value.compare (b64) != 0) return hash; else { s << "$pbkdf2$" << 2500 << "$" << SL_KEY_SALT << "$" << b64 << "$"; return s.str(); } } else return hash; } // New or updated value if (rounds != GetRounds (password.c_str())) { rounds = GetRounds (password.c_str()); if (RAND_bytes (salt, SL_AES_BLOCK_LEN) != 1) return hash; if (PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), (int)password.size(), salt, saltSize, rounds, SL_AES_KEY_LEN, skey) == 1 ) { sequencelogic::Base64Encode (salt, SL_AES_BLOCK_LEN, b64); s << "$pbkdf2$" << rounds << "$" << b64 << "$"; sequencelogic::Base64Encode (skey, SL_AES_KEY_LEN, b64); s << b64; return s.str(); } return hash; } // Validated password, no changes to rounds or algorithm else return value; } // Derive a key from the user password, can be updated to use bcrypt, scrypt or other in time // PBKDF2 (Password-Based Key Derivation Function 2) - also known as PKCS #5 v2.0 or RFC 2898 bool sequencelogic::DeriveKey (const char* password, unsigned char* key) { // Check for garbage input int len = (int)sequencelogic::StrLen (password, SL_PASSWORD_MAX_LEN); if (len == 0 || !key) return false; int rounds = GetRounds (password); unsigned char salt[8]; sequencelogic::HexToBinary (SL_KEY_SALT, salt); #ifdef TIME_DEBUG long ms; // System time in milliseconds ms = sequencelogic::GetSystemTime(); #endif if (PKCS5_PBKDF2_HMAC_SHA1(password, len, salt, SL_KEY_SALT_LEN, rounds, SL_AES_KEY_LEN, key) == 0 ) { return false; } #ifdef TIME_DEBUG ms = sequencelogic::GetSystemTime() - ms; if (ms < 1000) { unsigned char waste[SL_AES_KEY_LEN]; rounds = (1000 - ms) * (rounds / ms); PKCS5_PBKDF2_HMAC_SHA1(password, len, salt, SL_KEY_SALT_LEN, rounds, SL_AES_KEY_LEN, waste); } #endif RAND_add (key, SL_AES_KEY_LEN, 16.0); return true; } // CloudGaurd key has two parts, a 32 byte half key and the 32 byte PBKDF2 key // This method generates these values bool sequencelogic::GenerateCGKey (const char* password, unsigned char* key, unsigned char* halfkey) { // Check for garbage input int len = (int)sequencelogic::StrLen (password, SL_PASSWORD_MAX_LEN); if (len == 0 || !key || !halfkey) return false; int rounds = GetRounds (password); if (RAND_bytes (halfkey, SL_AES_KEY_LEN) != 1) return false; if (PKCS5_PBKDF2_HMAC_SHA1(password, len, halfkey, SL_AES_KEY_LEN, rounds, SL_AES_KEY_LEN, key) == 0 ) { return false; } return true; } // Validate that the user password and half key match match bool sequencelogic::ValidateCGKey (const char* password, const unsigned char* key, const unsigned char* halfkey) { // Check for garbage input int len = (int)sequencelogic::StrLen (password, SL_PASSWORD_MAX_LEN); if (len == 0 || !key || !halfkey) return false; int rounds = GetRounds (password); unsigned char vkey[SL_AES_KEY_LEN]; if (PKCS5_PBKDF2_HMAC_SHA1(password, len, halfkey, SL_AES_KEY_LEN, rounds, SL_AES_KEY_LEN, vkey) == 0 ) { return false; } return (memcmp (key, vkey, SL_AES_KEY_LEN) == 0); } // Generate the user key from the password and cg half key bool sequencelogic::DeriveUserKey (const char* password, unsigned char* userkey, const unsigned char* halfkey) { // Check for garbage input int len = (int)sequencelogic::StrLen (password, SL_PASSWORD_MAX_LEN); if (len == 0 || !userkey || !halfkey) return false; std::string hex = sequencelogic::DigestMessage (MESSAGE_DIGEST_SHA256, (unsigned char*)password, len); if (hex.size() != 2 * SL_AES_KEY_LEN) { return false; } sequencelogic::HexToBinary (hex.c_str(), userkey); sequencelogic::XorKeys (userkey, halfkey, userkey, SL_AES_KEY_LEN); return true; } bool sequencelogic::RandomBytes (size_t bytes, unsigned char* buffer) { if (RAND_bytes(buffer, (int)bytes)) return true; else return false; } std::string sequencelogic::RandomBase32 (size_t length) { std::string rb (""); if (length > 1 && length < 256) { char b32[256] = {}; unsigned char buffer[256]; size_t bytes = ((length+3) * 5) / 8; if (RAND_bytes(buffer, (int)bytes)) { sequencelogic::Base32Encode (buffer, bytes, b32); b32[length] = '\0'; rb = b32; return rb; } } IONUDEBUG ("sequencelogic::RandomBase32() - failed for %d length", (int)length); return rb; } std::string sequencelogic::RandomBase64 (size_t length) { std::string rb (""); if (length > 1 && length < 256) { char b64[256] = {}; unsigned char buffer[256]; size_t bytes = ((length+1) * 3) / 4; if (RAND_bytes(buffer, (int)bytes)) { sequencelogic::Base64Encode (buffer, bytes, b64); b64[length] = '\0'; rb = b64; return rb; } } IONUDEBUG ("sequencelogic::RandomBase64() - failed for %d length", (int)length); return rb; } // Default is the largest set of ascii characters that can be used static const char* passwordset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890`~!@#$%^&*()-_=+[]{}\\|;:'\",.<>/?"; std::string sequencelogic::RandomPassWord (size_t len, const char* charset) { if (! charset) charset = passwordset; size_t setlen = sequencelogic::StrLen (charset, 96); std::string password = ""; password.reserve (len); if (setlen >= 36) { // Must be at least base36 character set while (len > 0) { unsigned char c; RAND_bytes (&c, 1); size_t index = (size_t)c; if (index < setlen) { password += (charset[index]); len--; } } } return password; } void sequencelogic::DumpAsBinary (unsigned char* data, size_t bytes) { char* buff = new char[(bytes * 8) + 24]; char* bin = buff; for (size_t i = 0; i < bytes; ++i) { unsigned char b=0x80; for (int bit = 0; bit < 8; ++bit) { *bin++ = (data[i] & b) ? '1' : '0'; b = b >> 1; } *bin++ = ' '; } *bin++ = '\0'; printf ("%s\n", buff); } // Get the last system call error formatted as a string; std::string sequencelogic::GetLastErrorMessage() { std::string retVal; #ifdef WIN32 DWORD nErr = GetLastError(); retVal = GetErrorMessage(nErr); #endif return retVal; } std::string sequencelogic::GetErrorMessage(int nErrorCode) { std::string errmsg = ""; #ifdef WIN32 LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, nErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ); errmsg.assign ((LPTSTR)lpMsgBuf, lstrlen((LPCTSTR)lpMsgBuf)); LocalFree(lpMsgBuf); #else errmsg = strerror (errno); #endif return errmsg; } /** * Convert from std::string to std::wstring */ std::wstring sequencelogic::toWString(const std::string &str, const std::locale &loc /*= std::locale()*/) { if (str.size() == 0) return std::wstring(); std::vector tmpBuf(str.size()); std::use_facet >(loc).widen(str.data(), str.data()+str.size(), tmpBuf.data()); return std::wstring(tmpBuf.data(), tmpBuf.size()); } /** * Convert from std::wstring to std::string */ std::string sequencelogic::toAString(const std::wstring &wstr, const std::locale &loc /*= std::locale()*/) { if (wstr.size() == 0) return std::string(); std::vector tmpBuf(wstr.size()); std::use_facet >(loc).narrow(wstr.data(), wstr.data()+wstr.size(), '?', tmpBuf.data()); return std::string(tmpBuf.data(), tmpBuf.size()); } #ifdef WIN32 #define MAX_NAME 256 bool sequencelogic::GetLogonFromToken(HANDLE hToken, LPSTR lpName, LPSTR lpDomain) { DWORD dwSize = MAX_NAME; bool bSuccess = false; DWORD dwLength = 0; PTOKEN_USER ptu = NULL; if (!GetTokenInformation(hToken, // handle to the access token TokenUser, // get information about the token's groups (LPVOID) ptu, // pointer to PTOKEN_USER buffer 0, // size of buffer &dwLength)) { // receives required buffer size if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) return false; ptu = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength); if (ptu == NULL) return false; } if (GetTokenInformation(hToken, // handle to the access token TokenUser, // get information about the token's groups (LPVOID) ptu, // pointer to PTOKEN_USER buffer dwLength, // size of buffer &dwLength)) { // receives required buffer size SID_NAME_USE SidType; if( !LookupAccountSid (NULL, ptu->User.Sid, lpName, &dwSize, lpDomain, &dwSize, &SidType)) { DWORD dwResult = GetLastError(); IONUDEBUG ("LookupAccountSid Error %u\n", GetLastError()); } else { bSuccess = true; } } if (ptu != NULL) HeapFree (GetProcessHeap(), 0, (LPVOID)ptu); return bSuccess; } std::string sequencelogic::GetProcessOwner (DWORD processId) { std::string owner = ""; HANDLE hProcess = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, processId); if(hProcess == NULL) return owner; HANDLE hToken = NULL; if( !OpenProcessToken (hProcess, TOKEN_QUERY, &hToken)) { CloseHandle (hProcess); return owner; } char lpName[MAX_PATH] = ""; char lpDomain[MAX_NAME] = ""; if (GetLogonFromToken (hToken, lpName, lpDomain)) { owner = lpName; //IONUDEBUG ("Current user is %s\\%s\n", lpDomain, lpName); } CloseHandle (hToken); CloseHandle (hProcess); return owner; } std::string sequencelogic::GetProcessName (DWORD processId) { std::string process = ""; TCHAR szProcessName[MAX_PATH] = TEXT(""); // Get a handle to the process. HANDLE hProcess = OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId ); // Get the process name. if (NULL != hProcess ) { HMODULE hMod; DWORD cbNeeded; if (EnumProcessModules (hProcess, &hMod, sizeof(hMod), &cbNeeded)) { GetModuleBaseName (hProcess, hMod, szProcessName, sizeof(szProcessName)/sizeof(TCHAR)); } } // Release the handle to the process. CloseHandle( hProcess ); process = szProcessName; return process; } #else std::string sequencelogic::GetProcessOwner (pid_t processId) { std::string owner = ""; #ifdef LIN64 uid_t uid = getuid(); struct passwd* pass = getpwuid (uid); if (pass) owner = pass->pw_name; #endif return owner; } std::string sequencelogic::GetProcessName (pid_t processId) { std::string process = ""; #ifdef LIN64 char name[1024]; size_t start = 0; sprintf (name, "/proc/%d/cmdline", processId); FILE* f = fopen (name,"r"); if (f) { size_t size; size = fread (name, sizeof(char), 1024, f); if (size > 0) { for (size_t i = 0; i < size; ++i) { if (name[i] == ' ' || name[i] == '\n') { name[i] = '\0'; break; } else if (name[i] == '/') start = i + 1; } } fclose (f); process = &name[start]; } #endif return process; } #endif