Sleds/libeye/eyeutils.cpp

2080 lines
62 KiB
C++
Raw Normal View History

2025-03-13 21:28:38 +00:00
// Copyright (c) 2013-2015 IONU Security, Inc. All rights reserved.
// Copyright (c) 2016 Sequence Logic, Inc. All rights reserved.
//
// Utility functions and classes
#include <ctype.h>
#include <cstring>
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#ifdef WIN32
#include <io.h>
#include <Windows.h>
#include <psapi.h>
#include "zlib/win32/zconf.h"
#ifdef WIN64
typedef __int64 ssize_t;
#else
typedef __int32 ssize_t;
#endif
#else
#include <sys/time.h>
#include <errno.h>
#endif
#ifdef LIN64
#include <pwd.h>
#endif
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#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<int>(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<unsigned char>(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<volatile unsigned char*>(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<unsigned char>(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<unsigned char>(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<unsigned char>(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<unsigned char>(*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<std::string> sequencelogic::SplitString (const std::string& value, const char separator)
{
std::vector<std::string> 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, "<html>"))
mime = "text/html";
else if (strstr (buff, "<?xml"))
mime = "application/xml";
file.close();
}
}
}
}
return mime;
}
// Internal convenience routines for performance
size_t sequencelogic::Decryptor (EVP_CIPHER_CTX* ctx, size_t len, const unsigned char* ciphertext,
unsigned char* plaintext, const unsigned char* key,
const unsigned char* iv)
{
int bytes = 0;
int fbytes = 0;
EVP_DecryptInit_ex (ctx, EVP_aes_256_cbc(), NULL, key, iv);
if (!EVP_DecryptUpdate (ctx, plaintext, &bytes, ciphertext, static_cast<int>(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<int>(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<int>(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<std::string> 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<wchar_t> tmpBuf(str.size());
std::use_facet<std::ctype<wchar_t> >(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<char> tmpBuf(wstr.size());
std::use_facet<std::ctype<wchar_t> >(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("<unknown>");
// 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