Subversion Repositories tpanel

Rev

Rev 475 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2024 by Andreas Theofilu <andreas@theosys.at>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

#include <iostream>
#include <fstream>
#include <cstring>

#include <openssl/err.h>
#include <openssl/evp.h>

#include "tscramble.h"
#include "terror.h"

using std::string;
using std::ifstream;
using std::ios_base;
using std::ios;
using std::exception;
using std::min;
using std::cout;
using std::cerr;
using std::endl;

#define CHUNK_SIZE          1024
#define OSSL_SUCCESS        1
#define OSSL_ERROR          0
static unsigned long ssl_errno;

/*
 * Internally used helper functions
 */
void _print_ssl_error(unsigned long eno)
{
    char msg[1024];

    ERR_error_string_n(eno, msg, sizeof(msg));
    MSG_ERROR(msg);
}

string _get_ssl_error()
{
    char msg[1024];

    ERR_error_string_n(ssl_errno, msg, sizeof(msg));
    return string(msg);
}

/* -------------------------------------------------------------------------
 * Implementaion of methods from class TScramble
 * -------------------------------------------------------------------------*/
TScramble::TScramble()
{
    DECL_TRACER("TScramble::TScramble()");

    mCtx = EVP_CIPHER_CTX_new();

    if (!mCtx)
        MSG_ERROR("Error getting new context!");
}

TScramble::~TScramble()
{
    DECL_TRACER("TScramble::~TScramble()");

    if (mCtx)
    {
        EVP_CIPHER_CTX_cleanup(mCtx);
        EVP_CIPHER_CTX_free(mCtx);
    }
}

bool TScramble::aesInit(const string& key, const string& salt)
{
    DECL_TRACER("TScramble::aesInit(const string& key, const string& salt)");

    if (mAesInitialized)
        return true;

    if (!mCtx)
    {
        MSG_ERROR("ERROR: No context available! Initialisation failed!");
        return false;
    }

    int keySize;
    int count = 5;      // Number iterations

    const EVP_MD *md = EVP_sha1();
    const EVP_CIPHER *pCipher = EVP_aes_128_cbc();

    if (!md)
    {
        MSG_ERROR("Error getting SHA1 hash function!");
        return false;
    }

    if (!pCipher)
    {
        MSG_ERROR("Error getting the AES128-CBC cipher algorithm!");
        return false;
    }

    // If the given salt is less then AES128_SALT_SIZE bytes, we first initialize
    // the buffer with 0 bytes. Then the salt is copied into the buffer. This
    // guaranties that we have a padding.
    memset(mAesSalt, 0, AES128_SALT_SIZE);
    memcpy(mAesSalt, salt.c_str(), min((size_t)AES128_SALT_SIZE, salt.length()));
    // Initialize the key and IV with 0
    memset(mAesKey, 0, AES128_KEY_SIZE);
    memset(mAesIV, 0, AES128_KEY_SIZE);

    keySize = EVP_BytesToKey(pCipher, md, mAesSalt, (unsigned char *)key.c_str(), key.length(), count, mAesKey, mAesIV);

    if (keySize == AES128_KEY_SIZE)
    {
        EVP_CIPHER_CTX_init(mCtx);

        if ((ssl_errno = EVP_DecryptInit_ex(mCtx, pCipher, nullptr, mAesKey, mAesIV)) != OSSL_SUCCESS)
        {
            MSG_ERROR("Error initializing: " << _get_ssl_error());
            return false;
        }
 
        EVP_CIPHER_CTX_set_key_length(mCtx, AES128_KEY_SIZE);
    }
    else
    {
        MSG_ERROR("Key size is " << (keySize * 8) << " bits - should be 128 bits");
        return false;
    }

    mAesInitialized = true;
    return mAesInitialized;
}

bool TScramble::aesDecodeFile(const string& fname)
{
    DECL_TRACER("TScramble::aesDecodeFile(const string& fname)");

    if (fname.empty())
    {
        MSG_ERROR("Got no file name to open a file!");
        return false;
    }

    ifstream ifile;

    try
    {
        ifile.open(fname, ios::binary);
        bool state = aesDecodeFile(ifile);
        ifile.close();
        return state;
    }
    catch(exception& e)
    {
        MSG_ERROR("Error opening file \"" << fname << "\": " << e.what());

        if (ifile.is_open())
            ifile.close();
    }

    return false;
}

bool TScramble::aesDecodeFile(ifstream& is)
{
    DECL_TRACER("TScramble::aesDecodeFile(ifstream& is)");

    if (!is.is_open())
    {
        MSG_ERROR("No open file!");
        return false;
    }

    if (!mAesInitialized || !mCtx)
    {
        MSG_ERROR("Class was not initialized!");
        return false;
    }

    mDecrypted.clear();
    // Find file size
    is.seekg(0, ios_base::end);         // Seek to end of file
    size_t fileSize = is.tellg();       // Get current position (file size)
    is.seekg(0);                        // Seek to first byte of file
    size_t pos = 0;                     // Position (offset) in input and output buffer
    // Allocate space for input and output buffers
    unsigned char *buffer = new unsigned char[fileSize];
    unsigned char *outBuffer = new unsigned char[fileSize];
    unsigned char decBuffer[CHUNK_SIZE];    // Buffer for decoding a chunk.
    unsigned char encBuffer[CHUNK_SIZE];    // Buffer for decoding a chunk.
    int len = 0;
    // Not really necessary, but initialize output buffer with zeros
    memset(outBuffer, 0, fileSize);
    // Read whole file
    is.read(reinterpret_cast<char *>(buffer), fileSize);
    // decode
    if (fileSize > CHUNK_SIZE)     // Is the file size greater then a chunk?
    {                               // Yes, then start reading the file in chunks
        size_t numBlocks = fileSize / CHUNK_SIZE;

        for (size_t i = 0; i < numBlocks; ++i)
        {
            memcpy(encBuffer, buffer + i * CHUNK_SIZE, CHUNK_SIZE);

            if ((ssl_errno = EVP_DecryptUpdate(mCtx, decBuffer, &len, encBuffer, CHUNK_SIZE)) != OSSL_SUCCESS)
            {
                MSG_ERROR("Loop " << i << ": Error updating");
                _print_ssl_error(ssl_errno);
                delete[] buffer;
                delete[] outBuffer;
                return false;
            }

            memcpy(outBuffer+pos, decBuffer, len);
            pos += len;
        }

        int size2 = fileSize - (numBlocks * CHUNK_SIZE);

        if (size2 > 0)      // Is there something left of the file less then CHUNK_SIZE?
        {                   // Yes, then decrypt it
            memcpy(encBuffer, buffer + numBlocks * CHUNK_SIZE, size2);

            if ((ssl_errno = EVP_DecryptUpdate(mCtx, decBuffer, &len, encBuffer, size2)) != OSSL_SUCCESS)
            {
                MSG_ERROR("Error updating");
                _print_ssl_error(ssl_errno);
                delete[] buffer;
                delete[] outBuffer;
                return false;
            }

            memcpy(outBuffer+pos, decBuffer, len);
            pos += len;
        }
    }
    else
    {
        if ((ssl_errno = EVP_DecryptUpdate(mCtx, outBuffer, &len, buffer, fileSize)) != OSSL_SUCCESS)
        {
            _print_ssl_error(ssl_errno);
            delete[] buffer;
            delete[] outBuffer;
            return false;
        }

        pos = len;
    }

    if ((ssl_errno = EVP_DecryptFinal_ex(mCtx, outBuffer + pos, &len)) != OSSL_SUCCESS)
    {
        MSG_ERROR("Error finalizing: " << _get_ssl_error());
        delete[] buffer;
        delete[] outBuffer;
        return false;
    }

    pos += len;
    mDecrypted.assign(reinterpret_cast<char *>(outBuffer), pos);
    delete[] buffer;
    delete[] outBuffer;

    return true;
}